如何仅使用位移和加法进行乘法和除法?

时间:2010-05-05 19:35:23

标签: c assembly bit-manipulation division multiplication

如何仅使用位移和加法进行乘法和除法?

14 个答案:

答案 0 :(得分:68)

要在添加和移位方面相乘,您希望以2的幂分解其中一个数字,如下所示:

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表示基础2)

正如您所看到的,乘法可以分解为添加和移位,然后再返回。这也是为什么乘法比位移或添加更长的原因 - 它的位数是O(n ^ 2)而不是O(n)。真实计算机系统(与理论计算机系统相反)具有有限数量的比特,因此与加法和移位相比,乘法需要恒定的时间倍数。如果我没记错的话,现代处理器,如果管道正确,可以通过处理处理器中ALU(算术单元)的使用而与添加一样快地进行乘法运算。

答案 1 :(得分:41)

The answer by Andrew Toulouse可以扩展到分部。

整数常数的除法在Henry S. Warren的书“Hacker's Delight”中有详细考虑(ISBN 9780201914658)。

实现除法的第一个想法是在基数2中写出分母的逆值。

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

所以, a/3 = (a >> 2) + (a >> 4) + (a >> 6) + ... + (a >> 30)  用于32位算术。

通过以明显的方式组合这些术语,我们可以减少操作次数:

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

b += (b >> 4)

b += (b >> 8)

b += (b >> 16)

有更多令人兴奋的方法来计算除法和余数。

EDIT1:

如果OP意味着任意数的乘法和除法,而不是除以常数,则该线程可能有用:https://stackoverflow.com/a/12699549/1182653

EDIT2:

除以整数常数的最快方法之一是利用模块化算术和蒙哥马利减少:What's the fastest way to divide an integer by 3?

答案 2 :(得分:24)

X * 2 =左移1位 X / 2 =右移1位 X * 3 =向左移1位然后加X

答案 3 :(得分:21)

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

您可以使用这些移位来执行任何乘法运算。例如:

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

要将数字除以2的非幂,我不知道任何简单的方法,除非你想实现一些低级逻辑,使用其他二进制操作并使用某种形式的迭代。

答案 4 :(得分:17)

  1. 左移1个位置类似于乘以2.右移类似于除以2.
  2. 您可以添加一个循环来进行乘法运算。通过正确选择循环变量和加法变量,可以约束性能。一旦您进行了探索,就应该使用Peasant Multiplication

答案 5 :(得分:6)

我将Python代码翻译为C.给出的示例有一个小缺陷。如果占据了所有32位的被除数值,则转移将失败。我只是在内部使用64位变量来解决这个问题:

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 :(得分:4)

取两个数字,比如9和10,将它们写成二进制 - 1001和1010。

从结果R开始,为0。

取一个数字,在这种情况下为1010,我们将其称为A,并将其向右移一位,如果你移出一个,添加第一个数字,我们称之为B,到R

现在将B向左移一位并重复,直到所有位都移出A。

如果你看到它写出来的话会更容易看到发生了什么,这是一个例子:

      0
   0000      0
  10010      1
 000000      0
1001000      1
 ------
1011010

答案 7 :(得分:4)

分割使用移位和加法的整数的过程可以从小学教授的十进制长手分割中直接导出。每个商数字的选择都是简化的,因为数字为0和1:如果当前余数大于或等于除数,则部分商的最低有效位为1。

与小数长手除法一样,被除数的数字从最高有效到最低有效,一次一个数字。这很容易通过二元除法的左移来实现。此外,通过将当前商位左移一个位置,然后附加新商位来收集商位。

在经典布置中,这两个左移位组合成一个寄存器对的左移位。上半部分保持当前剩余部分,下半部分保持分红。由于通过左移将被除数位传送到余数寄存器,所以下半部分的未使用的最低有效位用于累加商位。

下面是x86汇编语言和该算法的C实现。这种特殊的变速器和变速器。添加分区有时被称为“不执行”&#34;变量,因为除非余数大于或等于除数,否则不执行从当前余数中除除除数。在C中,汇编版本在左移位寄存器对中没有使用进位标志的概念。相反,它是基于观察结果来模拟的,即模2 n 的加法结果可以更小,只有在执行时才加数。

#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

答案 8 :(得分:2)

取自here

这仅适用于分部:

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

答案 9 :(得分:2)

基本上是乘以除以基数2

左移= x * 2 ^ y

右移= x / 2 ^ y

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

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

答案 10 :(得分:1)

这应该适用于乘法:

.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

答案 11 :(得分:1)

下面的方法是二进制除法的实现,考虑到两个数字都是正数。如果减法是一个问题,我们也可以使用二元运算符来实现。

代码

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

用于乘法:

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

答案 12 :(得分:0)

对于对16位x86解决方案感兴趣的人, JasonKnight 这里有一段代码1(他还包括一个签名的乘法部分,我还没有测试过)。但是,该代码存在大输入问题,其中“add bx,bx”部分会溢出。

固定版本:

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

或者在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"
);

答案 13 :(得分:-1)

试试这个。 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])