ฉันจะคูณและหารโดยใช้การขยับและเพิ่มเพียงเล็กน้อยได้อย่างไร


86

ฉันจะคูณและหารโดยใช้การขยับและเพิ่มเพียงเล็กน้อยได้อย่างไร


20
เช่นเดียวกับที่คุณทำบนกระดาษในโรงเรียนมัธยมโดยใช้ไบนารีแทนทศนิยมเท่านั้น
Pascal Cuoq

1
@mtk: คำตอบนี้หายไปจากอะไร? คุณกำลังมองหาการใช้งาน C หรือแอสเซมบลีความกว้างตัวถูกดำเนินการโดยเฉพาะวิธีการหารเฉพาะ (เช่นการกู้คืนและการไม่คืนค่า)
njuffa

การลบตกลงหรือไม่? ดูเหมือนจะครอบคลุมหมดแล้ว
mksteve

คำถามนี้ต้องการอะไร การดำเนินการแปลการคูณและการหารของ CPU เป็นการเพิ่มบิตและการบวกหรือการลบแล้วและหากเป็นเช่นนั้นถ้าคอมไพเลอร์ยังไม่ได้ดำเนินการ
Kelly S. French

1
@ KellyS.French เพียงแค่อยากรู้อยากเห็นมันเป็นวิธีที่ดีกว่าในการจินตนาการว่าคอมไพเลอร์สามารถทำงานกับชุดคำสั่งที่ จำกัด ได้อย่างไร
Spidfire

คำตอบ:


77

ในการคูณในแง่ของการเพิ่มและการเลื่อนคุณต้องการสลายตัวเลขตัวใดตัวหนึ่งด้วยกำลังของสองดังนี้:

( _2หมายถึงฐาน 2)

อย่างที่คุณเห็นการคูณสามารถย่อยสลายเป็นการเพิ่มและเปลี่ยนและย้อนกลับได้ นี่คือสาเหตุที่การคูณใช้เวลานานกว่าการเปลี่ยนบิตหรือการเพิ่ม - เป็น O (n ^ 2) แทนที่จะเป็น O (n) ในจำนวนบิต ระบบคอมพิวเตอร์จริง (ตรงข้ามกับระบบคอมพิวเตอร์เชิงทฤษฎี) มีจำนวนบิต จำกัด ดังนั้นการคูณจึงใช้เวลาหลายค่าคงที่เมื่อเทียบกับการบวกและการขยับ ถ้าฉันจำได้อย่างถูกต้องโปรเซสเซอร์สมัยใหม่หากวางท่ออย่างถูกต้องสามารถทำการคูณได้เร็วพอ ๆ กับการเพิ่มโดยยุ่งกับการใช้ประโยชน์ของ ALU (หน่วยเลขคณิต) ในโปรเซสเซอร์


6
ฉันรู้ว่ามันผ่านมาแล้ว แต่คุณสามารถยกตัวอย่างด้วยการหารได้หรือไม่? ขอบคุณ
GniruT

42

คำตอบของ Andrew Toulouseสามารถขยายไปสู่การแบ่ง

การหารด้วยค่าคงที่จำนวนเต็มถือเป็นรายละเอียดในหนังสือ "Hacker's Delight" โดย Henry S. Warren (ISBN 9780201914658)

แนวคิดแรกสำหรับการใช้การหารคือการเขียนค่าผกผันของตัวส่วนในฐานสอง

เช่น, 1/3 = (base-2) 0.0101 0101 0101 0101 0101 0101 0101 0101 .....

ดังนั้น a/3 = (a >> 2) + (a >> 4) + (a >> 6) + ... + (a >> 30) สำหรับเลขคณิต 32 บิต

การรวมคำศัพท์เข้าด้วยกันอย่างชัดเจนเราสามารถลดจำนวนการดำเนินการ:

b = (a >> 2) + (a >> 4)

b += (b >> 4)

b += (b >> 8)

b += (b >> 16)

มีวิธีที่น่าตื่นเต้นมากขึ้นในการคำนวณการหารและส่วนที่เหลือ

แก้ไข 1:

หาก OP หมายถึงการคูณและการหารตัวเลขตามอำเภอใจไม่ใช่การหารด้วยจำนวนคงที่เธรดนี้อาจใช้งานได้: https://stackoverflow.com/a/12699549/1182653

แก้ไข 2:

วิธีที่เร็วที่สุดวิธีหนึ่งในการหารด้วยค่าคงที่จำนวนเต็มคือการใช้ประโยชน์จากการคำนวณแบบแยกส่วนและการลดมอนต์โกเมอรี: วิธีที่เร็วที่สุดในการหารจำนวนเต็มด้วย 3 คืออะไร?


ขอบคุณมากสำหรับข้อมูลอ้างอิงของ Hacker's Delight!
alecxe

2
อืมใช่คำตอบนี้ (หารด้วยค่าคงที่) ถูกต้องเพียงบางส่วน หากคุณพยายามทำ '3/3' คุณจะได้ 0 ใน Hacker's Delight พวกเขาอธิบายว่ามีข้อผิดพลาดที่คุณต้องชดเชย ในกรณีนี้: b += r * 11 >> 5ด้วยr = a - q * 3. ลิงก์: hackersdelight.org/divcMore.pdfหน้า 2+
atlaste

31

X * 2 = 1 บิตเลื่อนไปทางซ้าย
X / 2 = 1 บิตเลื่อนไปทางขวา
X * 3 = เลื่อนไปทางซ้าย 1 บิตแล้วเพิ่ม X


4
คุณหมายถึงadd Xคนสุดท้ายหรือไม่?
Mark Byers

1
ยังผิดอยู่ - บรรทัดสุดท้ายควรอ่าน: "X * 3 = เลื่อนไปทางซ้าย 1 บิตแล้วเพิ่ม X"
Paul R

1
"X / 2 = 1 บิตเลื่อนไปทางขวา" ไม่ใช่ทั้งหมด แต่จะปัดเศษลงเป็นอนันต์แทนที่จะเป็น 0 (สำหรับจำนวนลบ) ซึ่งเป็นการใช้การหารตามปกติ (อย่างน้อยที่สุดเท่าที่ฉันเคยเห็น)
Leif Andersen

25

x << k == x multiplied by 2 to the power of k
x >> k == x divided by 2 to the power of k

คุณสามารถใช้การเปลี่ยนแปลงเหล่านี้เพื่อทำการคูณใด ๆ ตัวอย่างเช่น:

x * 14 == x * 16 - x * 2 == (x << 4) - (x << 1)
x * 12 == x * 8 + x * 4 == (x << 3) + (x << 2)

ในการหารจำนวนด้วยจำนวนที่ไม่ใช่กำลังของสองฉันไม่ทราบวิธีง่ายๆใด ๆ เว้นแต่คุณต้องการใช้ตรรกะระดับต่ำให้ใช้การดำเนินการไบนารีอื่น ๆ และใช้รูปแบบการวนซ้ำบางรูปแบบ


@IVlad: คุณจะรวมการดำเนินการข้างต้นเพื่อดำเนินการอย่างไรหารด้วย 3?
Paul R

@ พอลอาร์ - จริงมันยากกว่า ฉันได้ชี้แจงคำตอบของฉันแล้ว
IVlad

การหารด้วยค่าคงที่ไม่ยากเกินไป (คูณด้วยค่าคงที่เวทย์มนตร์แล้วหารด้วยกำลัง 2) แต่การหารด้วยตัวแปรนั้นยากกว่าเล็กน้อย
Paul R

1
ไม่ควร x * 14 == x * 16 - x * 2 == (x << 4) - (x << 2) จบลงด้วยการเป็น (x << 4) - (x << 1) ตั้งแต่ x < <1 คูณด้วย x ด้วย 2?
Alex Spencer

18
  1. การเลื่อนไปทางซ้ายทีละ 1 ตำแหน่งนั้นคล้ายคลึงกับการคูณด้วย 2 การเลื่อนไปทางขวานั้นคล้ายคลึงกับการหารด้วย 2
  2. คุณสามารถเพิ่มลูปเพื่อคูณได้ ด้วยการเลือกตัวแปรลูปและตัวแปรเพิ่มเติมอย่างถูกต้องคุณสามารถผูกประสิทธิภาพ เมื่อคุณสำรวจแล้วคุณควรใช้การคูณชาวนา

9
1: แต่การเปลี่ยนแปลงทางด้านซ้ายไม่ได้เป็นเพียงคล้ายคลึงกับการคูณด้วย 2 มันจะคูณด้วย 2 อย่างน้อยก็จนกว่าล้น ...
ดอนโรบี

Shift-division ให้ผลลัพธ์ที่ไม่ถูกต้องสำหรับจำนวนลบ
David

6

ฉันแปลรหัส Python เป็น C ตัวอย่างที่ให้มามีข้อบกพร่องเล็กน้อย หากค่าปันผลที่กินครบทั้ง 32 บิตกะจะล้มเหลว ฉันเพิ่งใช้ตัวแปร 64 บิตภายในเพื่อแก้ไขปัญหา:


แล้วจำนวนลบล่ะ? ฉันทดสอบ -12345 กับ 10 โดยใช้ eclipse + CDT แต่ผลลัพธ์ไม่ดีเท่าไหร่
kenmux

คุณช่วยบอกฉันได้ไหมว่าทำไมคุณถึงทำullDivisor >>= 1ก่อนwhileลูป นอกจากนี้จะไม่nPos >= 0ทำเคล็ดลับ?
Vivekanand V

@kenmux คุณต้องพิจารณาเฉพาะขนาดของตัวเลขที่เกี่ยวข้องก่อนอื่นให้ทำอัลกอริทึมจากนั้นใช้คำสั่งในการตัดสินใจที่เหมาะสมส่งคืนเครื่องหมายที่เหมาะสมไปยังผลหาร / เศษ!
Vivekanand V

1
@VivekanandV คุณหมายถึงเพิ่มเครื่องหมาย - ในภายหลัง? ใช่มันใช้งานได้
kenmux

5

ขั้นตอนในการหารจำนวนเต็มที่ใช้การกะและการบวกสามารถหาได้อย่างตรงไปตรงมาจากการหารระยะยาวทศนิยมตามที่สอนในโรงเรียนประถมศึกษา การเลือกหลักผลหารแต่ละตัวจะทำให้ง่ายขึ้นเนื่องจากหลักเป็น 0 และ 1: ถ้าเศษปัจจุบันมากกว่าหรือเท่ากับตัวหารบิตที่มีนัยสำคัญน้อยที่สุดของผลหารบางส่วนคือ 1

เช่นเดียวกับการหารระยะยาวทศนิยมตัวเลขของเงินปันผลจะพิจารณาจากที่มีนัยสำคัญมากที่สุดไปยังที่มีนัยสำคัญน้อยที่สุดทีละหนึ่งหลัก สิ่งนี้ทำได้อย่างง่ายดายโดยการเลื่อนไปทางซ้ายในการหารไบนารี นอกจากนี้บิตผลหารจะถูกรวบรวมโดยการเลื่อนบิตผลหารปัจจุบันไปทางซ้ายทีละตำแหน่งจากนั้นผนวกบิตผลหารใหม่

ในการจัดเรียงแบบคลาสสิกการเลื่อนด้านซ้ายทั้งสองนี้จะรวมกันเป็นการขยับซ้ายของคู่ทะเบียน ครึ่งบนถือส่วนที่เหลือปัจจุบันครึ่งล่างเริ่มต้นถือเงินปันผล เนื่องจากบิตเงินปันผลถูกโอนไปยังรีจิสเตอร์ส่วนที่เหลือโดยการเลื่อนด้านซ้ายบิตที่มีนัยสำคัญน้อยที่สุดที่ไม่ได้ใช้ของครึ่งล่างจะถูกใช้เพื่อสะสมบิตผลหาร

ด้านล่างนี้คือภาษาแอสเซมบลี x86 และการใช้งาน C ของอัลกอริทึมนี้ ตัวแปรเฉพาะของการแบ่ง shift & add นี้บางครั้งเรียกว่าตัวแปร "ไม่มีประสิทธิภาพ" เนื่องจากการลบตัวหารจากเศษปัจจุบันจะไม่ดำเนินการเว้นแต่ว่าส่วนที่เหลือจะมากกว่าหรือเท่ากับตัวหาร ใน C ไม่มีแนวคิดเกี่ยวกับแฟล็กพกพาที่ใช้โดยเวอร์ชันแอสเซมบลีในการเลื่อนซ้ายของคู่ทะเบียน แต่จะจำลองขึ้นจากการสังเกตว่าผลลัพธ์ของการเพิ่มโมดูโล 2 nอาจมีขนาดเล็กลงซึ่งอาจเพิ่มได้ก็ต่อเมื่อมีการดำเนินการ


@greybeard ขอบคุณสำหรับตัวชี้คุณถูกต้องฉันผสมเงินปันผลกับผลหาร ฉันจะแก้ไขมัน
njuffa

4

หาตัวเลขสองตัวสมมติว่า 9 และ 10 เขียนเป็นเลขฐานสอง - 1001 และ 1010

เริ่มต้นด้วยผลลัพธ์ R จาก 0

ใช้หนึ่งในจำนวน 1010 ในกรณีนี้เราจะเรียกมันว่า A และเลื่อนไปทีละนิดถ้าคุณเลื่อนตัวเลขออกให้เพิ่มหมายเลขแรกเราจะเรียกมันว่า B ถึง R

ตอนนี้เลื่อน B ไปทางซ้ายทีละหนึ่งบิตและทำซ้ำจนกว่าบิตทั้งหมดจะถูกเลื่อนออกจาก A

มันง่ายกว่าที่จะดูว่าเกิดอะไรขึ้นถ้าคุณเห็นมันเขียนออกมานี่คือตัวอย่าง


ดูเหมือนว่าเร็วที่สุดเพียงแค่ต้องการการเข้ารหัสเพิ่มเติมเล็กน้อยเพื่อวนผ่านบิตของตัวเลขที่น้อยที่สุดและคำนวณผลลัพธ์
Hellonearthis

2

นำมาจากที่นี่ .

นี่เป็นเพียงการหารเท่านั้น:


2

โดยพื้นฐานแล้วมันคือการคูณและหารด้วยกำลังฐาน 2

เลื่อนไปทางซ้าย = x * 2 ^ y

เลื่อนไปทางขวา = x / 2 ^ y

shl eax, 2 = 2 * 2 ^ 2 = 8

shr eax, 3 = 2/2 ^ 3 = 1/4


eax1/4ไม่สามารถถือเป็นค่าเศษส่วนเช่น (ยกเว้นกรณีที่คุณใช้จุดคงที่แทนจำนวนเต็ม แต่คุณไม่ได้ระบุไว้)
Peter Cordes

1

สิ่งนี้ควรใช้สำหรับการคูณ:


1
มันคือการประกอบ MIPS ถ้านี่คือสิ่งที่คุณกำลังถาม ฉันคิดว่าฉันใช้ MARS ในการเขียน / รันมัน
Melsi

1

วิธีการด้านล่างนี้คือการใช้การหารไบนารีโดยพิจารณาว่าตัวเลขทั้งสองเป็นค่าบวก หากการลบเป็นข้อกังวลเราสามารถใช้สิ่งนั้นได้เช่นกันโดยใช้ตัวดำเนินการไบนารี

รหัส

สำหรับการคูณ:


ไวยากรณ์นี้คืออะไร? -(int)multiplyNumber:(int)num1 withNumber:(int)num2เหรอ?
SS Anne

0

สำหรับใครที่สนใจโซลูชัน x86 16 บิตมีโค้ดส่วนหนึ่งของJasonKnightอยู่ที่นี่1 (เขายังรวมถึงชิ้นส่วนคูณที่มีลายเซ็นซึ่งฉันยังไม่ได้ทดสอบ) อย่างไรก็ตามโค้ดดังกล่าวมีปัญหากับอินพุตขนาดใหญ่ซึ่งส่วน "add bx, bx" จะล้น

เวอร์ชันคงที่:

หรือเหมือนกันในแอสเซมบลีแบบอินไลน์ของ GCC:


-1

ลองทำตามนี้ https://gist.github.com/swguru/5219592


5
ดูเหมือนงูหลาม คำถามถูกถามสำหรับการชุมนุมและ / หรือ C.
โมฆะ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.