Làm thế nào tôi có thể nhân và chia chỉ sử dụng dịch chuyển và cộng bit?
Làm thế nào tôi có thể nhân và chia chỉ sử dụng dịch chuyển và cộng bit?
Câu trả lời:
Để nhân về phép cộng và chuyển, bạn muốn phân tích một trong các số thành lũy thừa của hai, như sau:
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
nghĩa là cơ số 2)
Như bạn thấy, phép nhân có thể được phân tách thành phép cộng và dịch chuyển và lùi lại. Đây cũng là lý do tại sao phép nhân mất nhiều thời gian hơn so với dịch chuyển bit hoặc cộng - đó là O (n ^ 2) chứ không phải là O (n) về số lượng bit. Các hệ thống máy tính thực (trái ngược với các hệ thống máy tính lý thuyết) có số lượng bit hữu hạn, vì vậy phép nhân cần một bội số thời gian không đổi so với phép cộng và phép chuyển. Nếu tôi nhớ lại một cách chính xác, các bộ xử lý hiện đại, nếu được ghép nối đúng cách, có thể thực hiện phép nhân nhanh như phép cộng, bằng cách sử dụng các ALU (đơn vị số học) trong bộ xử lý.
Câu trả lời của Andrew Toulouse có thể được mở rộng thành phân chia.
Sự phân chia theo hằng số nguyên được xem xét chi tiết trong cuốn sách "Hacker's Delight" của Henry S. Warren (ISBN 9780201914658).
Ý tưởng đầu tiên để thực hiện phép chia là viết giá trị nghịch đảo của mẫu số trong cơ số hai.
Ví dụ,
1/3 = (base-2) 0.0101 0101 0101 0101 0101 0101 0101 0101 .....
Vì vậy,
a/3 = (a >> 2) + (a >> 4) + (a >> 6) + ... + (a >> 30)
đối với số học 32 bit.
Bằng cách kết hợp các thuật ngữ một cách rõ ràng, chúng ta có thể giảm số lượng hoạt động:
b = (a >> 2) + (a >> 4)
b += (b >> 4)
b += (b >> 8)
b += (b >> 16)
Có nhiều cách thú vị hơn để tính phép chia và phần dư.
CHỈNH SỬA1:
Nếu OP có nghĩa là nhân và chia các số tùy ý, không phải là phép chia cho một số không đổi, thì chủ đề này có thể được sử dụng: https://stackoverflow.com/a/12699549/1182653
EDIT2:
Một trong những cách nhanh nhất để chia cho các hằng số nguyên là khai thác số học mô-đun và giảm Montgomery: Cách nhanh nhất để chia một số nguyên cho 3 là gì?
b += r * 11 >> 5
với r = a - q * 3
. Liên kết: hackerdelight.org/divcMore.pdf trang 2+.
X * 2 = 1 bit dịch sang trái
X / 2 = 1 bit dịch sang phải
X * 3 = dịch sang trái 1 bit và sau đó thêm X
add X
cuối cùng?
x << k == x multiplied by 2 to the power of k
x >> k == x divided by 2 to the power of k
Bạn có thể sử dụng các thay đổi này để thực hiện bất kỳ hoạt động nhân nào. Ví dụ:
x * 14 == x * 16 - x * 2 == (x << 4) - (x << 1)
x * 12 == x * 8 + x * 4 == (x << 3) + (x << 2)
Để chia một số cho lũy thừa của hai, tôi không biết có cách nào dễ dàng, trừ khi bạn muốn thực hiện một số logic cấp thấp, hãy sử dụng các phép toán nhị phân khác và sử dụng một số dạng lặp.
Tôi đã dịch mã Python sang C. Ví dụ được đưa ra có một lỗ hổng nhỏ. Nếu giá trị cổ tức chiếm tất cả 32 bit, quá trình chuyển đổi sẽ không thành công. Tôi chỉ sử dụng các biến 64-bit nội bộ để khắc phục sự cố:
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
trước while
vòng lặp không? Ngoài ra, sẽ không nPos >= 0
làm thủ thuật?
Thủ tục chia số nguyên sử dụng dịch chuyển và phép cộng có thể được rút ra theo cách đơn giản từ phép chia số thập phân như được dạy ở trường tiểu học. Việc lựa chọn từng chữ số thương được đơn giản hóa, vì chữ số là 0 và 1: nếu phần dư hiện tại lớn hơn hoặc bằng số chia, bit có nghĩa nhỏ nhất của thương từng phần là 1.
Cũng giống như với phép chia tay dài thập phân, các chữ số của số bị chia được coi là từ quan trọng nhất đến quan trọng nhất, một chữ số tại một thời điểm. Điều này có thể dễ dàng thực hiện bằng cách dịch chuyển sang trái trong phép chia nhị phân. Ngoài ra, các bit thương được tập hợp bằng cách dịch sang trái các bit thương hiện tại theo một vị trí, sau đó nối thêm bit thương mới.
Trong một cách sắp xếp cổ điển, hai sự dịch chuyển trái này được kết hợp thành sự dịch chuyển trái của một cặp thanh ghi. Nửa trên giữ phần còn lại hiện tại, nửa dưới ban đầu giữ cổ tức. Khi các bit cổ tức được chuyển sang thanh ghi phần còn lại bằng cách dịch chuyển sang trái, các bit có ý nghĩa nhỏ nhất không được sử dụng của nửa dưới được sử dụng để tích lũy các bit thương.
Dưới đây là hợp ngữ x86 và các triển khai C của thuật toán này. Biến thể cụ thể này của phép chia shift & cộng đôi khi được gọi là biến thể "không hoạt động", vì phép trừ số chia khỏi phần dư hiện tại không được thực hiện trừ khi phần dư lớn hơn hoặc bằng số chia. Trong C, không có khái niệm cờ thực hiện được sử dụng bởi phiên bản hợp ngữ trong dịch chuyển trái của cặp thanh ghi. Thay vào đó, nó được mô phỏng, dựa trên quan sát rằng kết quả của một modulo cộng 2 n có thể nhỏ hơn mà một trong hai chỉ được bổ sung nếu có thực hiệ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
Lấy hai số, giả sử là 9 và 10, viết chúng dưới dạng nhị phân - 1001 và 1010.
Bắt đầu với kết quả, R, là 0.
Lấy một trong các số, 1010 trong trường hợp này, chúng ta sẽ gọi nó là A và dịch nó sang phải một bit, nếu bạn chuyển ra một số, hãy thêm số đầu tiên, chúng tôi sẽ gọi nó là B, thành R.
Bây giờ dịch chuyển B sang trái một bit và lặp lại cho đến khi tất cả các bit đã được dịch chuyển ra khỏi A.
Sẽ dễ dàng hơn để xem những gì đang xảy ra nếu bạn thấy nó được viết ra, đây là ví dụ:
0
0000 0
10010 1
000000 0
1001000 1
------
1011010
Lấy từ đây .
Điều này chỉ dành cho phân chia:
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;
}
Về cơ bản nó là nhân và chia với lũy thừa cơ bản 2
dịch sang trái = x * 2 ^ y
dịch sang phải = x / 2 ^ y
shl eax, 2 = 2 * 2 ^ 2 = 8
shr eax, 3 = 2/2 ^ 3 = 1/4
eax
không thể giữ một giá trị phân số như 1/4
. (Trừ khi bạn đang sử dụng điểm cố định thay vì số nguyên, nhưng bạn không chỉ định điều đó)
Điều này sẽ hoạt động để nhân:
.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
Phương pháp dưới đây là thực hiện phép chia nhị phân xem xét cả hai số đều dương. Nếu phép trừ là một mối quan tâm, chúng ta có thể thực hiện điều đó bằng cách sử dụng các toán tử nhị phân.
-(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);
}
Đối với phép nhân:
-(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
?
Đối với bất kỳ ai quan tâm đến giải pháp x86 16-bit, có một đoạn mã của JasonKnight ở đây 1 (anh ấy cũng bao gồm một đoạn nhân có chữ ký, mà tôi chưa thử nghiệm). Tuy nhiên, mã đó có vấn đề với đầu vào lớn, trong đó phần "thêm bx, bx" sẽ tràn.
Phiên bản cố định:
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
Hoặc tương tự trong lắp ráp nội tuyến 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"
);
Thử đi. 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])