Come posso moltiplicare e dividere usando solo lo spostamento e l'aggiunta di bit?
Come posso moltiplicare e dividere usando solo lo spostamento e l'aggiunta di bit?
Risposte:
Per moltiplicare in termini di aggiunta e spostamento, devi scomporre uno dei numeri per potenze di due, in questo modo:
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
significa base 2)
Come puoi vedere, la moltiplicazione può essere scomposta in aggiunta, spostamento e ritorno. Questo è anche il motivo per cui la moltiplicazione richiede più tempo degli spostamenti di bit o dell'aggiunta: è O (n ^ 2) invece di O (n) nel numero di bit. I sistemi informatici reali (al contrario dei sistemi informatici teorici) hanno un numero finito di bit, quindi la moltiplicazione richiede un multiplo costante del tempo rispetto all'addizione e allo spostamento. Se ricordo bene, i processori moderni, se pipeline correttamente, possono eseguire la moltiplicazione alla stessa velocità dell'addizione, interferendo con l'utilizzo delle ALU (unità aritmetiche) nel processore.
La risposta di Andrew Toulouse può essere estesa alla divisione.
La divisione per costanti intere è considerata in dettaglio nel libro "Hacker's Delight" di Henry S. Warren (ISBN 9780201914658).
La prima idea per implementare la divisione è scrivere il valore inverso del denominatore in base due.
Per esempio,
1/3 = (base-2) 0.0101 0101 0101 0101 0101 0101 0101 0101 .....
Quindi,
a/3 = (a >> 2) + (a >> 4) + (a >> 6) + ... + (a >> 30)
per aritmetica a 32 bit.
Combinando i termini in modo ovvio possiamo ridurre il numero di operazioni:
b = (a >> 2) + (a >> 4)
b += (b >> 4)
b += (b >> 8)
b += (b >> 16)
Esistono modi più interessanti per calcolare divisione e resto.
EDIT1:
Se l'OP indica la moltiplicazione e la divisione di numeri arbitrari, non la divisione per un numero costante, allora questo thread potrebbe essere utile: https://stackoverflow.com/a/12699549/1182653
EDIT2:
Uno dei modi più veloci per dividere per costanti intere è sfruttare l'aritmetica modulare e la riduzione di Montgomery: qual è il modo più veloce per dividere un numero intero per 3?
b += r * 11 >> 5
con r = a - q * 3
. Link: hackersdelight.org/divcMore.pdf pagina 2+.
X * 2 = spostamento di 1 bit a sinistra
X / 2 = spostamento di 1 bit a destra
X * 3 = spostamento a sinistra di 1 bit e quindi aggiunta di X
add X
per l'ultimo?
x << k == x multiplied by 2 to the power of k
x >> k == x divided by 2 to the power of k
È possibile utilizzare questi turni per eseguire qualsiasi operazione di moltiplicazione. Per esempio:
x * 14 == x * 16 - x * 2 == (x << 4) - (x << 1)
x * 12 == x * 8 + x * 4 == (x << 3) + (x << 2)
Per dividere un numero per una non potenza di due, non sono a conoscenza di alcun modo semplice, a meno che non si desideri implementare una logica di basso livello, utilizzare altre operazioni binarie e utilizzare una qualche forma di iterazione.
Ho tradotto il codice Python in C. L'esempio fornito aveva un piccolo difetto. Se il valore del dividendo che ha occupato tutti i 32 bit, lo spostamento fallirebbe. Ho appena usato internamente variabili a 64 bit per aggirare il problema:
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
prima del while
ciclo? Inoltre, non funzionerà nPos >= 0
?
Una procedura per la divisione di numeri interi che utilizza turni e aggiunte può essere derivata in modo semplice dalla divisione decimale a mano lunga come insegnata nella scuola elementare. La selezione di ciascuna cifra del quoziente è semplificata, poiché la cifra è 0 e 1: se il resto corrente è maggiore o uguale al divisore, il bit meno significativo del quoziente parziale è 1.
Proprio come con la divisione decimale a mano lunga, le cifre del dividendo sono considerate dalla più significativa alla meno significativa, una cifra alla volta. Ciò è facilmente realizzabile con uno spostamento a sinistra nella divisione binaria. Inoltre, i bit di quoziente vengono raccolti spostando a sinistra i bit di quoziente corrente di una posizione, quindi aggiungendo il nuovo bit di quoziente.
In un arrangiamento classico, questi due spostamenti a sinistra sono combinati nello spostamento a sinistra di una coppia di registri. La metà superiore detiene il resto corrente, la metà inferiore iniziale detiene il dividendo. Quando i bit di dividendo vengono trasferiti al registro del resto mediante spostamento a sinistra, i bit meno significativi inutilizzati della metà inferiore vengono utilizzati per accumulare i bit di quoziente.
Di seguito è riportato il linguaggio assembly x86 e le implementazioni C di questo algoritmo. Questa particolare variante di una divisione shift & add viene talvolta definita variante "non performante", poiché la sottrazione del divisore dal resto corrente non viene eseguita a meno che il resto non sia maggiore o uguale al divisore. In C, non vi è alcuna nozione del flag di riporto utilizzato dalla versione assembly nello spostamento a sinistra della coppia di registri. Viene invece emulato, basandosi sull'osservazione che il risultato di un'addizione modulo 2 n può essere più piccolo dell'una o dell'altra solo se c'è stata una esecuzione.
#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
Prendi due numeri, diciamo 9 e 10, scrivili come binari - 1001 e 1010.
Inizia con un risultato, R, di 0.
Prendi uno dei numeri, 1010 in questo caso, lo chiameremo A, e spostalo a destra di un bit, se ne sposti uno, aggiungi il primo numero, lo chiameremo B, a R.
Ora sposta B a sinistra di un bit e ripeti finché tutti i bit non sono stati spostati da A.
È più facile vedere cosa sta succedendo se lo vedi scritto, questo è l'esempio:
0
0000 0
10010 1
000000 0
1001000 1
------
1011010
Preso da qui .
Questo è solo per la divisione:
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;
}
fondamentalmente si moltiplica e si divide con la potenza di base 2
sposta a sinistra = x * 2 ^ y
sposta a destra = x / 2 ^ y
shl eax, 2 = 2 * 2 ^ 2 = 8
shr eax, 3 = 2/2 ^ 3 = 1/4
eax
non può contenere un valore frazionario come 1/4
. (A meno che tu non stia utilizzando il punto fisso anziché il numero intero, ma non l'hai specificato)
Questo dovrebbe funzionare per la moltiplicazione:
.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
Il metodo seguente è l'implementazione del divario binario considerando che entrambi i numeri sono positivi. Se la sottrazione è un problema, possiamo implementarlo anche utilizzando operatori binari.
-(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);
}
Per la moltiplicazione:
-(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
?
Per chiunque sia interessato a una soluzione x86 a 16 bit, c'è un pezzo di codice di JasonKnight qui 1 (include anche un pezzo multiplo firmato, che non ho testato). Tuttavia, quel codice ha problemi con input di grandi dimensioni, dove la parte "add bx, bx" andrebbe in overflow.
La versione fissa:
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
O lo stesso nell'assemblaggio in linea di 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"
);
Prova questo. 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])