Comment puis-je multiplier et diviser en n'utilisant que le transfert et l'ajout de bits?
Pour multiplier en termes d'ajout et de décalage, vous voulez décomposer l'un des nombres par des puissances de deux, comme suit:
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 la base 2)
Comme vous pouvez le constater, la multiplication peut être décomposée en ajout, déplacement et retour. C’est aussi pourquoi la multiplication prend plus de temps que les décalages ou l’ajout de bits - c’est O (n ^ 2) plutôt que O(n) en nombre de bits. Systèmes informatiques réels (par opposition à un ordinateur théorique systèmes) ont un nombre fini de bits, donc la multiplication prend un multiple de temps constant par rapport à l’addition et au décalage. des ALU (unités arithmétiques) dans le processeur.
La réponse de Andrew Toulouse peut être étendu à la division.
La division par les constantes entières est décrite en détail dans le livre "Hacker's Delight" de Henry S. Warren (ISBN 9780201914658).
La première idée pour mettre en œuvre la division est d'écrire l'inverse du dénominateur en base deux.
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 les arithmétiques 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 moyens plus intéressants de calculer la division et les restes.
EDIT1:
Si l'OP signifie la multiplication et la division de nombres arbitraires, et non la division par un nombre constant, alors ce fil peut être utile: https://stackoverflow.com/a/12699549/118265
EDIT2:
L'un des moyens les plus rapides de diviser par des constantes entières consiste à exploiter l'arithmétique modulaire et la réduction de Montgomery: Quel est le moyen le plus rapide de diviser un entier par 3?
X * 2 = décalage de 1 bit à gauche
X/2 = décalage de 1 bit à droite
X * 3 = décaler de 1 bit à gauche puis ajouter X
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 un non-pouvoir de deux, je ne connais aucun moyen simple, sauf si vous souhaitez implémenter une logique de bas niveau, utiliser d'autres opérations binaires et utiliser une certaine forme d'itération.
J'ai traduit le code Python en C. L'exemple donné comportait un défaut mineur. Si la valeur du dividende occupait les 32 bits, le décalage échouerait. Je viens d'utiliser des variables de 64 bits en interne. 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;
}
Prenez deux nombres, disons 9 et 10, écrivez-les sous forme 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. Si vous en décalez un, ajoutez le premier numéro, nous l'appellerons B, à R.
Déplacez maintenant B de 1 bit et répétez l'opération jusqu'à ce que tous les bits soient 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
Une procédure de division des nombres entiers, qui utilise des décalages et des ajouts, peut être dérivée directement de la division décimale à la longue décimale enseignée à l’école élémentaire. La sélection de chaque chiffre du quotient est simplifiée, car le chiffre est égal à 0 et 1: si le reste actuel est supérieur ou égal au diviseur, le bit le moins significatif du quotient partiel est égal à 1.
Comme pour la division décimale dans la partie longue, les chiffres du dividende sont considérés comme allant du plus significatif au moins significatif, un chiffre à la fois. Ceci est facilement accompli par un décalage à gauche de la division binaire. De plus, les bits de quotient sont rassemblés en décalant à gauche les bits de quotient actuels d'une position, puis en ajoutant le nouveau bit de quotient.
Dans un arrangement classique, ces deux décalages à gauche sont combinés en un décalage à 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 dans le registre restant par décalage à 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 effectuée que si le reste est supérieur ou égal au diviseur. En C, il n'y a aucune notion d'indicateur de report utilisé par la version Assembly dans le décalage gauche de la paire de registres. Au lieu de cela, il est émulé, basé sur l'observation que le résultat d'une addition modulo 2n peut être plus petit que ce soit ajouter seulement s'il y avait 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
Tiré de ici .
Ceci est seulement 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;
}
La méthode ci-dessous est la mise en œuvre de la division binaire en considérant les deux nombres sont positifs. Si la soustraction est un problème, nous pouvons également l'implémenter 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;
}
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
Pour ceux qui sont intéressés par une solution x86 16 bits, il y a un morceau de code de JasonKnight ici 1 (il inclut également une pièce multipliée signée, que je n'ai pas testée). Toutefois, ce code présente des problèmes avec des entrées volumineuses, où la partie "add bx, bx" déborderait.
La version corrigée:
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 GCC inline Assembly:
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"
);