web-dev-qa-db-fra.com

Comment puis-je multiplier et diviser en n'utilisant que le transfert et l'ajout de bits?

Comment puis-je multiplier et diviser en n'utilisant que le transfert et l'ajout de bits?

81
Spidfire

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.

68
Andrew Toulouse

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?

41
Viktor Latypov

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

25
Kelly S. French

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.

21
IVlad
  1. Un décalage à gauche par 1 équivaut à une multiplication par 2. Un décalage à droite équivaut à une division par 2.
  2. Vous pouvez ajouter une boucle pour se multiplier. En choisissant correctement la variable de boucle et la variable d’ajout, vous pouvez lier les performances. Une fois que vous avez exploré cela, vous devriez utiliser Multiplication Paysanne
17
Yann Ramin

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;
}
6
user2954726

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
4
Jimmeh

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
4
njuffa

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;
}
2
Ashish Ahuja

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.

Code

-(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;
}
1
muzz

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
1
Melsi

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"
);
0
axic