ฉันจะคูณและหารโดยใช้การขยับและเพิ่มเพียงเล็กน้อยได้อย่างไร
ฉันจะคูณและหารโดยใช้การขยับและเพิ่มเพียงเล็กน้อยได้อย่างไร
คำตอบ:
ในการคูณในแง่ของการเพิ่มและการเลื่อนคุณต้องการสลายตัวเลขตัวใดตัวหนึ่งด้วยกำลังของสองดังนี้:
21 * 5 = 10101_2 * 101_2 (Initial step)
= 10101_2 * (1 * 2^2 + 0 * 2^1 + 1 * 2^0)
= 10101_2 * 2^2 + 10101_2 * 2^0
= 10101_2 << 2 + 10101_2 << 0 (Decomposed)
= 10101_2 * 4 + 10101_2 * 1
= 10101_2 * 5
= 21 * 5 (Same as initial expression)
( _2
หมายถึงฐาน 2)
อย่างที่คุณเห็นการคูณสามารถย่อยสลายเป็นการเพิ่มและเปลี่ยนและย้อนกลับได้ นี่คือสาเหตุที่การคูณใช้เวลานานกว่าการเปลี่ยนบิตหรือการเพิ่ม - เป็น O (n ^ 2) แทนที่จะเป็น O (n) ในจำนวนบิต ระบบคอมพิวเตอร์จริง (ตรงข้ามกับระบบคอมพิวเตอร์เชิงทฤษฎี) มีจำนวนบิต จำกัด ดังนั้นการคูณจึงใช้เวลาหลายค่าคงที่เมื่อเทียบกับการบวกและการขยับ ถ้าฉันจำได้อย่างถูกต้องโปรเซสเซอร์สมัยใหม่หากวางท่ออย่างถูกต้องสามารถทำการคูณได้เร็วพอ ๆ กับการเพิ่มโดยยุ่งกับการใช้ประโยชน์ของ ALU (หน่วยเลขคณิต) ในโปรเซสเซอร์
คำตอบของ 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 คืออะไร?
b += r * 11 >> 5
ด้วยr = a - q * 3
. ลิงก์: hackersdelight.org/divcMore.pdfหน้า 2+
X * 2 = 1 บิตเลื่อนไปทางซ้าย
X / 2 = 1 บิตเลื่อนไปทางขวา
X * 3 = เลื่อนไปทางซ้าย 1 บิตแล้วเพิ่ม X
add X
คนสุดท้ายหรือไม่?
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)
ในการหารจำนวนด้วยจำนวนที่ไม่ใช่กำลังของสองฉันไม่ทราบวิธีง่ายๆใด ๆ เว้นแต่คุณต้องการใช้ตรรกะระดับต่ำให้ใช้การดำเนินการไบนารีอื่น ๆ และใช้รูปแบบการวนซ้ำบางรูปแบบ
ฉันแปลรหัส Python เป็น C ตัวอย่างที่ให้มามีข้อบกพร่องเล็กน้อย หากค่าปันผลที่กินครบทั้ง 32 บิตกะจะล้มเหลว ฉันเพิ่งใช้ตัวแปร 64 บิตภายในเพื่อแก้ไขปัญหา:
int No_divide(int nDivisor, int nDividend, int *nRemainder)
{
int nQuotient = 0;
int nPos = -1;
unsigned long long ullDivisor = nDivisor;
unsigned long long ullDividend = nDividend;
while (ullDivisor < ullDividend)
{
ullDivisor <<= 1;
nPos ++;
}
ullDivisor >>= 1;
while (nPos > -1)
{
if (ullDividend >= ullDivisor)
{
nQuotient += (1 << nPos);
ullDividend -= ullDivisor;
}
ullDivisor >>= 1;
nPos -= 1;
}
*nRemainder = (int) ullDividend;
return nQuotient;
}
ullDivisor >>= 1
ก่อนwhile
ลูป นอกจากนี้จะไม่nPos >= 0
ทำเคล็ดลับ?
ขั้นตอนในการหารจำนวนเต็มที่ใช้การกะและการบวกสามารถหาได้อย่างตรงไปตรงมาจากการหารระยะยาวทศนิยมตามที่สอนในโรงเรียนประถมศึกษา การเลือกหลักผลหารแต่ละตัวจะทำให้ง่ายขึ้นเนื่องจากหลักเป็น 0 และ 1: ถ้าเศษปัจจุบันมากกว่าหรือเท่ากับตัวหารบิตที่มีนัยสำคัญน้อยที่สุดของผลหารบางส่วนคือ 1
เช่นเดียวกับการหารระยะยาวทศนิยมตัวเลขของเงินปันผลจะพิจารณาจากที่มีนัยสำคัญมากที่สุดไปยังที่มีนัยสำคัญน้อยที่สุดทีละหนึ่งหลัก สิ่งนี้ทำได้อย่างง่ายดายโดยการเลื่อนไปทางซ้ายในการหารไบนารี นอกจากนี้บิตผลหารจะถูกรวบรวมโดยการเลื่อนบิตผลหารปัจจุบันไปทางซ้ายทีละตำแหน่งจากนั้นผนวกบิตผลหารใหม่
ในการจัดเรียงแบบคลาสสิกการเลื่อนด้านซ้ายทั้งสองนี้จะรวมกันเป็นการขยับซ้ายของคู่ทะเบียน ครึ่งบนถือส่วนที่เหลือปัจจุบันครึ่งล่างเริ่มต้นถือเงินปันผล เนื่องจากบิตเงินปันผลถูกโอนไปยังรีจิสเตอร์ส่วนที่เหลือโดยการเลื่อนด้านซ้ายบิตที่มีนัยสำคัญน้อยที่สุดที่ไม่ได้ใช้ของครึ่งล่างจะถูกใช้เพื่อสะสมบิตผลหาร
ด้านล่างนี้คือภาษาแอสเซมบลี x86 และการใช้งาน C ของอัลกอริทึมนี้ ตัวแปรเฉพาะของการแบ่ง shift & add นี้บางครั้งเรียกว่าตัวแปร "ไม่มีประสิทธิภาพ" เนื่องจากการลบตัวหารจากเศษปัจจุบันจะไม่ดำเนินการเว้นแต่ว่าส่วนที่เหลือจะมากกว่าหรือเท่ากับตัวหาร ใน C ไม่มีแนวคิดเกี่ยวกับแฟล็กพกพาที่ใช้โดยเวอร์ชันแอสเซมบลีในการเลื่อนซ้ายของคู่ทะเบียน แต่จะจำลองขึ้นจากการสังเกตว่าผลลัพธ์ของการเพิ่มโมดูโล 2 nอาจมีขนาดเล็กลงซึ่งอาจเพิ่มได้ก็ต่อเมื่อมีการดำเนินการ
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#define USE_ASM 0
#if USE_ASM
uint32_t bitwise_division (uint32_t dividend, uint32_t divisor)
{
uint32_t quot;
__asm {
mov eax, [dividend];// quot = dividend
mov ecx, [divisor]; // divisor
mov edx, 32; // bits_left
mov ebx, 0; // rem
$div_loop:
add eax, eax; // (rem:quot) << 1
adc ebx, ebx; // ...
cmp ebx, ecx; // rem >= divisor ?
jb $quot_bit_is_0; // if (rem < divisor)
$quot_bit_is_1: //
sub ebx, ecx; // rem = rem - divisor
add eax, 1; // quot++
$quot_bit_is_0:
dec edx; // bits_left--
jnz $div_loop; // while (bits_left)
mov [quot], eax; // quot
}
return quot;
}
#else
uint32_t bitwise_division (uint32_t dividend, uint32_t divisor)
{
uint32_t quot, rem, t;
int bits_left = CHAR_BIT * sizeof (uint32_t);
quot = dividend;
rem = 0;
do {
// (rem:quot) << 1
t = quot;
quot = quot + quot;
rem = rem + rem + (quot < t);
if (rem >= divisor) {
rem = rem - divisor;
quot = quot + 1;
}
bits_left--;
} while (bits_left);
return quot;
}
#endif
หาตัวเลขสองตัวสมมติว่า 9 และ 10 เขียนเป็นเลขฐานสอง - 1001 และ 1010
เริ่มต้นด้วยผลลัพธ์ R จาก 0
ใช้หนึ่งในจำนวน 1010 ในกรณีนี้เราจะเรียกมันว่า A และเลื่อนไปทีละนิดถ้าคุณเลื่อนตัวเลขออกให้เพิ่มหมายเลขแรกเราจะเรียกมันว่า B ถึง R
ตอนนี้เลื่อน B ไปทางซ้ายทีละหนึ่งบิตและทำซ้ำจนกว่าบิตทั้งหมดจะถูกเลื่อนออกจาก A
มันง่ายกว่าที่จะดูว่าเกิดอะไรขึ้นถ้าคุณเห็นมันเขียนออกมานี่คือตัวอย่าง
0
0000 0
10010 1
000000 0
1001000 1
------
1011010
นำมาจากที่นี่ .
นี่เป็นเพียงการหารเท่านั้น:
int add(int a, int b) {
int partialSum, carry;
do {
partialSum = a ^ b;
carry = (a & b) << 1;
a = partialSum;
b = carry;
} while (carry != 0);
return partialSum;
}
int subtract(int a, int b) {
return add(a, add(~b, 1));
}
int division(int dividend, int divisor) {
boolean negative = false;
if ((dividend & (1 << 31)) == (1 << 31)) { // Check for signed bit
negative = !negative;
dividend = add(~dividend, 1); // Negation
}
if ((divisor & (1 << 31)) == (1 << 31)) {
negative = !negative;
divisor = add(~divisor, 1); // Negation
}
int quotient = 0;
long r;
for (int i = 30; i >= 0; i = subtract(i, 1)) {
r = (divisor << i);
// Left shift divisor until it's smaller than dividend
if (r < Integer.MAX_VALUE && r >= 0) { // Avoid cases where comparison between long and int doesn't make sense
if (r <= dividend) {
quotient |= (1 << i);
dividend = subtract(dividend, (int) r);
}
}
}
if (negative) {
quotient = add(~quotient, 1);
}
return quotient;
}
โดยพื้นฐานแล้วมันคือการคูณและหารด้วยกำลังฐาน 2
เลื่อนไปทางซ้าย = x * 2 ^ y
เลื่อนไปทางขวา = x / 2 ^ y
shl eax, 2 = 2 * 2 ^ 2 = 8
shr eax, 3 = 2/2 ^ 3 = 1/4
eax
1/4
ไม่สามารถถือเป็นค่าเศษส่วนเช่น (ยกเว้นกรณีที่คุณใช้จุดคงที่แทนจำนวนเต็ม แต่คุณไม่ได้ระบุไว้)
สิ่งนี้ควรใช้สำหรับการคูณ:
.data
.text
.globl main
main:
# $4 * $5 = $2
addi $4, $0, 0x9
addi $5, $0, 0x6
add $2, $0, $0 # initialize product to zero
Loop:
beq $5, $0, Exit # if multiplier is 0,terminate loop
andi $3, $5, 1 # mask out the 0th bit in multiplier
beq $3, $0, Shift # if the bit is 0, skip add
addu $2, $2, $4 # add (shifted) multiplicand to product
Shift:
sll $4, $4, 1 # shift up the multiplicand 1 bit
srl $5, $5, 1 # shift down the multiplier 1 bit
j Loop # go for next
Exit: #
EXIT:
li $v0,10
syscall
วิธีการด้านล่างนี้คือการใช้การหารไบนารีโดยพิจารณาว่าตัวเลขทั้งสองเป็นค่าบวก หากการลบเป็นข้อกังวลเราสามารถใช้สิ่งนั้นได้เช่นกันโดยใช้ตัวดำเนินการไบนารี
-(int)binaryDivide:(int)numerator with:(int)denominator
{
if (numerator == 0 || denominator == 1) {
return numerator;
}
if (denominator == 0) {
#ifdef DEBUG
NSAssert(denominator==0, @"denominator should be greater then 0");
#endif
return INFINITY;
}
// if (numerator <0) {
// numerator = abs(numerator);
// }
int maxBitDenom = [self getMaxBit:denominator];
int maxBitNumerator = [self getMaxBit:numerator];
int msbNumber = [self getMSB:maxBitDenom ofNumber:numerator];
int qoutient = 0;
int subResult = 0;
int remainingBits = maxBitNumerator-maxBitDenom;
if (msbNumber >= denominator) {
qoutient |=1;
subResult = msbNumber - denominator;
}
else {
subResult = msbNumber;
}
while (remainingBits > 0) {
int msbBit = (numerator & (1 << (remainingBits-1)))>0?1:0;
subResult = (subResult << 1) | msbBit;
if(subResult >= denominator) {
subResult = subResult - denominator;
qoutient= (qoutient << 1) | 1;
}
else{
qoutient = qoutient << 1;
}
remainingBits--;
}
return qoutient;
}
-(int)getMaxBit:(int)inputNumber
{
int maxBit = 0;
BOOL isMaxBitSet = NO;
for (int i=0; i<sizeof(inputNumber)*8; i++) {
if (inputNumber & (1<<i)) {
maxBit = i;
isMaxBitSet=YES;
}
}
if (isMaxBitSet) {
maxBit+=1;
}
return maxBit;
}
-(int)getMSB:(int)bits ofNumber:(int)number
{
int numbeMaxBit = [self getMaxBit:number];
return number >> (numbeMaxBit - bits);
}
สำหรับการคูณ:
-(int)multiplyNumber:(int)num1 withNumber:(int)num2
{
int mulResult = 0;
int ithBit;
BOOL isNegativeSign = (num1<0 && num2>0) || (num1>0 && num2<0);
num1 = abs(num1);
num2 = abs(num2);
for (int i=0; i<sizeof(num2)*8; i++)
{
ithBit = num2 & (1<<i);
if (ithBit>0) {
mulResult += (num1 << i);
}
}
if (isNegativeSign) {
mulResult = ((~mulResult)+1);
}
return mulResult;
}
-(int)multiplyNumber:(int)num1 withNumber:(int)num2
เหรอ?
สำหรับใครที่สนใจโซลูชัน x86 16 บิตมีโค้ดส่วนหนึ่งของJasonKnightอยู่ที่นี่1 (เขายังรวมถึงชิ้นส่วนคูณที่มีลายเซ็นซึ่งฉันยังไม่ได้ทดสอบ) อย่างไรก็ตามโค้ดดังกล่าวมีปัญหากับอินพุตขนาดใหญ่ซึ่งส่วน "add bx, bx" จะล้น
เวอร์ชันคงที่:
softwareMultiply:
; INPUT CX,BX
; OUTPUT DX:AX - 32 bits
; CLOBBERS BX,CX,DI
xor ax,ax ; cheap way to zero a reg
mov dx,ax ; 1 clock faster than xor
mov di,cx
or di,bx ; cheap way to test for zero on both regs
jz @done
mov di,ax ; DI used for reg,reg adc
@loop:
shr cx,1 ; divide by two, bottom bit moved to carry flag
jnc @skipAddToResult
add ax,bx
adc dx,di ; reg,reg is faster than reg,imm16
@skipAddToResult:
add bx,bx ; faster than shift or mul
adc di,di
or cx,cx ; fast zero check
jnz @loop
@done:
ret
หรือเหมือนกันในแอสเซมบลีแบบอินไลน์ของ GCC:
asm("mov $0,%%ax\n\t"
"mov $0,%%dx\n\t"
"mov %%cx,%%di\n\t"
"or %%bx,%%di\n\t"
"jz done\n\t"
"mov %%ax,%%di\n\t"
"loop:\n\t"
"shr $1,%%cx\n\t"
"jnc skipAddToResult\n\t"
"add %%bx,%%ax\n\t"
"adc %%di,%%dx\n\t"
"skipAddToResult:\n\t"
"add %%bx,%%bx\n\t"
"adc %%di,%%di\n\t"
"or %%cx,%%cx\n\t"
"jnz loop\n\t"
"done:\n\t"
: "=d" (dx), "=a" (ax)
: "b" (bx), "c" (cx)
: "ecx", "edi"
);
ลองทำตามนี้ https://gist.github.com/swguru/5219592
import sys
# implement divide operation without using built-in divide operator
def divAndMod_slow(y,x, debug=0):
r = 0
while y >= x:
r += 1
y -= x
return r,y
# implement divide operation without using built-in divide operator
def divAndMod(y,x, debug=0):
## find the highest position of positive bit of the ratio
pos = -1
while y >= x:
pos += 1
x <<= 1
x >>= 1
if debug: print "y=%d, x=%d, pos=%d" % (y,x,pos)
if pos == -1:
return 0, y
r = 0
while pos >= 0:
if y >= x:
r += (1 << pos)
y -= x
if debug: print "y=%d, x=%d, r=%d, pos=%d" % (y,x,r,pos)
x >>= 1
pos -= 1
return r, y
if __name__ =="__main__":
if len(sys.argv) == 3:
y = int(sys.argv[1])
x = int(sys.argv[2])
else:
y = 313271356
x = 7
print "=== Slow Version ...."
res = divAndMod_slow( y, x)
print "%d = %d * %d + %d" % (y, x, res[0], res[1])
print "=== Fast Version ...."
res = divAndMod( y, x, debug=1)
print "%d = %d * %d + %d" % (y, x, res[0], res[1])