Comment puis-je multiplier et diviser en utilisant uniquement le décalage et l'ajout de bits?
Comment puis-je multiplier et diviser en utilisant uniquement le décalage et l'ajout de bits?
Réponses:
Pour multiplier en termes d'ajout et de décalage, vous voulez décomposer l'un des nombres par des puissances de deux, comme ceci:
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
signifie base 2)
Comme vous pouvez le voir, la multiplication peut être décomposée en ajout et en décalage et inversement. C'est aussi pourquoi la multiplication prend plus de temps que les décalages de bits ou l'addition - c'est O (n ^ 2) plutôt que O (n) dans le nombre de bits. Les systèmes informatiques réels (par opposition aux systèmes informatiques théoriques) ont un nombre fini de bits, de sorte que la multiplication prend un multiple constant de temps par rapport à l'addition et au décalage. Si je me souviens bien, les processeurs modernes, s'ils sont correctement mis en pipeline, peuvent faire la multiplication à peu près aussi vite que l'addition, en jouant avec l'utilisation des ALU (unités arithmétiques) dans le processeur.
La réponse d'Andrew Toulouse peut être étendue à la division.
La division par constantes entières est examinée en détail dans le livre "Hacker's Delight" de Henry S. Warren (ISBN 9780201914658).
La première idée pour implémenter la division est d'écrire la valeur inverse du dénominateur en base deux.
Par exemple,
1/3 = (base-2) 0.0101 0101 0101 0101 0101 0101 0101 0101 .....
Donc,
a/3 = (a >> 2) + (a >> 4) + (a >> 6) + ... + (a >> 30)
pour l'arithmétique 32 bits.
En combinant les termes de manière évidente, nous pouvons réduire le nombre d'opérations:
b = (a >> 2) + (a >> 4)
b += (b >> 4)
b += (b >> 8)
b += (b >> 16)
Il existe des méthodes plus intéressantes pour calculer la division et les restes.
EDIT1:
Si l'OP signifie multiplication et division de nombres arbitraires, pas la division par un nombre constant, alors ce fil pourrait être utile: https://stackoverflow.com/a/12699549/1182653
EDIT2:
L'un des moyens les plus rapides de diviser par des constantes entières est d'exploiter l'arithmétique modulaire et la réduction de Montgomery: quel est le moyen le plus rapide de diviser un entier par 3?
b += r * 11 >> 5
avec r = a - q * 3
. Lien: hackersdelight.org/divcMore.pdf page 2+.
X * 2 = décalage de 1 bit vers la gauche
X / 2 = décalage de 1 bit vers la droite
X * 3 = décalage de 1 bit vers la gauche, puis additionnez X
add X
pour ce dernier?
x << k == x multiplied by 2 to the power of k
x >> k == x divided by 2 to the power of k
Vous pouvez utiliser ces décalages pour effectuer n'importe quelle opération de multiplication. Par exemple:
x * 14 == x * 16 - x * 2 == (x << 4) - (x << 1)
x * 12 == x * 8 + x * 4 == (x << 3) + (x << 2)
Pour diviser un nombre par une non-puissance de deux, je ne connais aucun moyen facile, à moins que vous ne souhaitiez implémenter une logique de bas niveau, utiliser d'autres opérations binaires et utiliser une forme d'itération.
J'ai traduit le code Python en C. L'exemple donné avait un petit défaut. Si la valeur de dividende occupait tous les 32 bits, le décalage échouerait. Je viens d'utiliser des variables 64 bits en interne pour contourner le problème:
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
avant la while
boucle? De plus, ne fera pas nPos >= 0
l'affaire?
Une procédure de division des nombres entiers qui utilise des décalages et des ajouts peut être dérivée de manière directe de la division décimale à la main comme enseignée à l'école élémentaire. La sélection de chaque chiffre du quotient est simplifiée, car le chiffre est soit 0 soit 1: si le reste courant est supérieur ou égal au diviseur, le bit le moins significatif du quotient partiel est 1.
Tout comme pour la division décimale à la main, les chiffres du dividende sont considérés du plus significatif au moins significatif, un chiffre à la fois. Ceci est facilement accompli par un décalage vers la gauche dans la division binaire. De plus, les bits de quotient sont rassemblés en décalant vers la gauche les bits de quotient actuels d'une position, puis en ajoutant le nouveau bit de quotient.
Dans un agencement classique, ces deux décalages vers la gauche sont combinés en un décalage vers la gauche d'une paire de registres. La moitié supérieure détient le reste actuel, la moitié inférieure initiale détient le dividende. Lorsque les bits de dividende sont transférés vers le registre de reste par décalage vers la gauche, les bits les moins significatifs non utilisés de la moitié inférieure sont utilisés pour accumuler les bits de quotient.
Vous trouverez ci-dessous le langage d'assemblage x86 et les implémentations C de cet algorithme. Cette variante particulière d'une division shift & add est parfois appelée variante "non performante", car la soustraction du diviseur du reste actuel n'est pas effectuée à moins que le reste soit supérieur ou égal au diviseur. En C, il n'y a pas de notion du drapeau de retenue utilisé par la version d'assemblage dans le décalage gauche de la paire de registres. Au lieu de cela, il est émulé, sur la base de l'observation que le résultat d'une addition modulo 2 n peut être plus petit que l'ajout uniquement s'il y a eu une exécution.
#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
Prenez deux nombres, disons 9 et 10, écrivez-les en binaire - 1001 et 1010.
Commencez avec un résultat, R, de 0.
Prenez l'un des nombres, 1010 dans ce cas, nous l'appellerons A, et décalerons-le d'un bit vers la droite, si vous en décalez un, ajoutez le premier numéro, nous l'appellerons B, à R.
Maintenant, décalez B vers la gauche d'un bit et répétez jusqu'à ce que tous les bits aient été décalés de A.
Il est plus facile de voir ce qui se passe si vous le voyez écrit, voici l'exemple:
0
0000 0
10010 1
000000 0
1001000 1
------
1011010
Pris d' ici .
Ceci est uniquement pour la division:
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;
}
c'est essentiellement multiplier et diviser avec la puissance de base 2
décalage gauche = x * 2 ^ y
décalage vers la droite = x / 2 ^ y
shl eax, 2 = 2 * 2 ^ 2 = 8
shr eax, 3 = 2/2 ^ 3 = 1/4
eax
ne peut pas contenir une valeur fractionnaire comme 1/4
. (Sauf si vous utilisez la virgule fixe au lieu d'un entier, mais vous ne l'avez pas spécifié)
Cela devrait fonctionner pour la multiplication:
.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
La méthode ci-dessous est la mise en œuvre de la division binaire en considérant que les deux nombres sont positifs. Si la soustraction est un problème, nous pouvons l'implémenter également en utilisant des opérateurs binaires.
-(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);
}
Pour la multiplication:
-(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
?
Pour toute personne intéressée par une solution x86 16 bits, il existe un morceau de code de JasonKnight ici 1 (il comprend également un morceau de multiplication signé, que je n'ai pas testé). Cependant, ce code a des problèmes avec des entrées volumineuses, où la partie "add bx, bx" déborderait.
La version fixe:
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
Ou la même chose dans l'assemblage en ligne 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"
);
Essaye ça. 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])