Come posso moltiplicare e dividere usando solo lo spostamento e l'aggiunta di bit?


86

Come posso moltiplicare e dividere usando solo lo spostamento e l'aggiunta di bit?


20
Come faresti sulla carta alle medie, usando solo binario invece che decimale.
Pascal Cuoq

1
@mtk: cosa manca a questa risposta ? Stai cercando un'implementazione C o assembly, larghezze di operandi particolari, un metodo di divisione specifico (es. Ripristino vs non ripristino)?
njuffa

La sottrazione va bene? Tutto sembra essere coperto
mksteve

Qual è il bisogno dietro questa domanda? Le CPU traducono le operazioni di moltiplicazione e divisione in cambio di bit e addizione o sottrazione già, e se questo è se il compilatore non lo ha già fatto.
Kelly S. French

@ KellyS.French Solo curiosità, è più un modo per immaginare come un compilatore possa lavorare con un set di istruzioni limitato.
Spidfire

Risposte:


77

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)

( _2significa 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.


4
So che è stato tempo fa, ma potresti fare un esempio con la divisione? Grazie
GniruT

42

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?


Grazie mille per il riferimento di Hacker's Delight!
alecxe

2
Ehm sì, questa risposta (divisione per costante) è corretta solo parzialmente . Se provi a fare "3/3" ti ritroverai con 0. In Hacker's Delight, in realtà spiegano che c'è un errore che devi compensare. In questo caso: b += r * 11 >> 5con r = a - q * 3. Link: hackersdelight.org/divcMore.pdf pagina 2+.
atlante

30

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


4
Intendi add Xper l'ultimo?
Mark Byers

1
È ancora sbagliato - l'ultima riga dovrebbe leggere: "X * 3 = sposta a sinistra di 1 bit e poi aggiungi X"
Paul R

1
"X / 2 = 1 bit shift right", non del tutto, arrotonda per difetto all'infinito, piuttosto che fino a 0 (per i numeri negativi), che è la consueta implementazione della divisione (almeno per quanto ho visto).
Leif Andersen

25

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.


@IVlad: come combinereste le operazioni di cui sopra per eseguire, diciamo, dividere per 3?
Paul R

@ Paul R - vero, è più difficile. Ho chiarito la mia risposta.
IVlad

la divisione per una costante non è troppo difficile (moltiplicare per costante magica e poi dividere per una potenza di 2), ma la divisione per una variabile è un po 'più complicata.
Paul R

1
non dovrebbe x * 14 == x * 16 - x * 2 == (x << 4) - (x << 2) finire davvero per essere (x << 4) - (x << 1) poiché x < <1 si moltiplica per x per 2?
Alex Spencer

18
  1. Uno spostamento a sinistra di 1 posizione è analogo alla moltiplicazione per 2. Uno spostamento a destra è analogo alla divisione per 2.
  2. Puoi aggiungere in un ciclo per moltiplicare. Selezionando correttamente la variabile di ciclo e la variabile di addizione, è possibile collegare le prestazioni. Dopo averlo esplorato, dovresti usare la moltiplicazione contadina

9
+1: Ma lo spostamento a sinistra non è solo analoga a moltiplicare per 2. Si è moltiplicando per 2. Almeno fino troppo pieno ...
Don Roby

La divisione Shift produce risultati errati per i numeri negativi.
David

6

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

E il numero negativo? Ho testato -12345 con 10 usando eclipse + CDT, ma il risultato non è stato così buono.
kenmux

Puoi dirmi perché lo fai ullDivisor >>= 1prima del whileciclo? Inoltre, non funzionerà nPos >= 0?
Vivekanand V

@kenmux Devi considerare solo l'ampiezza dei numeri coinvolti, prima fai l'algoritmo e poi usando alcune appropriate dichiarazioni decisionali, restituisci il segno corretto al quoziente / resto!
Vivekanand V

1
@VivekanandV Intendi aggiungere il segno - più tardi? Sì funziona.
kenmux

5

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

@greybeard Grazie per il puntatore, hai ragione, ho confuso il dividendo con il quoziente. Lo aggiusterò.
njuffa

4

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

Questo sembra più veloce, richiede solo un po 'di codice extra per scorrere i bit del numero più piccolo e calcolare il risultato.
Hellonearthis

2

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

2

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


eaxnon 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)
Peter Cordes

1

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

Che sapore di assemblaggio?
Keith Pinson

1
È l'assemblaggio MIPS, se questo è ciò che stai chiedendo. Penso di aver usato MARS per scriverlo / eseguirlo.
Melsi

1

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.

Codice

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

Qual è questa sintassi? -(int)multiplyNumber:(int)num1 withNumber:(int)num2?
SS Anne

0

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

-1

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])

5
Questo sembra pitone. La domanda è stata posta per assemblea e / o C.
nulla il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.