How can I multiply and divide using only bit shifting and adding?

后端 未结 14 1622
清歌不尽
清歌不尽 2020-11-22 15:09

How can I multiply and divide using only bit shifting and adding?

相关标签:
14条回答
  • 2020-11-22 15:27

    it is basically multiplying and dividing with the base power 2

    shift left = x * 2 ^ y

    shift right = x / 2 ^ y

    shl eax,2 = 2 * 2 ^ 2 = 8

    shr eax,3 = 2 / 2 ^ 3 = 1/4

    0 讨论(0)
  • 2020-11-22 15:27

    For anyone interested in a 16-bit x86 solution, there is a piece of code by JasonKnight here1 (he also includes a signed multiply piece, which I haven't tested). However, that code has issues with large inputs, where the "add bx,bx" part would overflow.

    The fixed version:

    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
    

    Or the same in 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 讨论(0)
  • 2020-11-22 15:29

    x << k == x multiplied by 2 to the power of k
    x >> k == x divided by 2 to the power of k

    You can use these shifts to do any multiplication operation. For example:

    x * 14 == x * 16 - x * 2 == (x << 4) - (x << 1)
    x * 12 == x * 8 + x * 4 == (x << 3) + (x << 2)

    To divide a number by a non-power of two, I'm not aware of any easy way, unless you want to implement some low-level logic, use other binary operations and use some form of iteration.

    0 讨论(0)
  • 2020-11-22 15:35

    The answer by Andrew Toulouse can be extended to division.

    The division by integer constants is considered in details in the book "Hacker's Delight" by Henry S. Warren (ISBN 9780201914658).

    The first idea for implementing division is to write the inverse value of the denominator in base two.

    E.g., 1/3 = (base-2) 0.0101 0101 0101 0101 0101 0101 0101 0101 .....

    So, a/3 = (a >> 2) + (a >> 4) + (a >> 6) + ... + (a >> 30) for 32-bit arithmetics.

    By combining the terms in an obvious manner we can reduce the number of operations:

    b = (a >> 2) + (a >> 4)

    b += (b >> 4)

    b += (b >> 8)

    b += (b >> 16)

    There are more exciting ways to calculate division and remainders.

    EDIT1:

    If the OP means multiplication and division of arbitrary numbers, not the division by a constant number, then this thread might be of use: https://stackoverflow.com/a/12699549/1182653

    EDIT2:

    One of the fastest ways to divide by integer constants is to exploit the modular arithmetics and Montgomery reduction: What's the fastest way to divide an integer by 3?

    0 讨论(0)
  • 2020-11-22 15:36

    I translated the Python code to C. The example given had a minor flaw. If the dividend value that took up all the 32 bits, the shift would fail. I just used 64-bit variables internally to work around the problem:

    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;
    }
    
    0 讨论(0)
  • 2020-11-22 15:36

    Taken from here.

    This is only for 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;
    }
    
    0 讨论(0)
提交回复
热议问题