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


86

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


20
就像您在中学时用纸做的一样,只使用二进制而不是十进制。
Pascal Cuoq

1
@mtk:此答案缺少什么?您是否在寻找C或汇编实现,特定的操作数宽度,特定的除法(例如,还原还是非还原)?
njuffa

减法可以吗?所有内容似乎都被掩盖了
mksteve

这个问题背后需要什么?CPU的乘法和除法运算已经将其转换为位移位,加法或减法,如果是的话,则编译器还没有这样做。
凯利·法国

@ KellyS.French出于好奇,它是一种想象编译器如何使用受限指令集的方法。
Spidfire

Answers:


77

要进行加法和移位运算,您需要将两个数的幂分解为一个数,如下所示:

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)

(以_22为底)

如您所见,乘法可以分解为加法和移位然后再返回。这也是为什么乘法需要比移位或相加更长的时间的原因-位数是O(n ^ 2)而不是O(n)。实际的计算机系统(与理论计算机系统相对)具有有限的位数,因此与加法和移位相比,乘法需要恒定的时间倍数。如果我没记错的话,现代的处理器(如果正确地进行了流水线处理)可以通过扰乱处理器中ALU(算术单元)的使用来进行乘法。


4
我知道已经有一段时间了,但是您能举例说明除法吗?谢谢
GniruT 2015年

42

Andrew Toulouse的答案可以扩展到除法。

Henry S. Warren(ISBN 9780201914658)所著的“ Hacker's Delight”一书中详细考虑了整数常量的除法。

实现除法的第一个想法是在分母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)

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

编辑1:

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

编辑2:

除以整数常量最快的方法之一是利用模算和蒙哥马利约简:将整数除以3的最快方法是什么?


非常感谢Hacker's Delight参考!
alecxe

2
嗯,是的,这个答案(除以常数)只是部分正确的。如果您尝试执行“ 3/3”,则结果将为0。在Hacker's Delight中,他们实际上解释了必须补偿的错误。在这种情况下:b += r * 11 >> 5使用r = a - q * 3。链接:hackersdelight.org/divcMore.pdf第2+页。
atlaste'Apr

30

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


4
你是说add X那最后一个吗?
Mark Byers 2010年

1
它仍然是错误的-最后一行应显示为:“ X * 3 =向左移1位,然后加X”
Paul R 2010年

1
“ X / 2 = 1右移”不完全取整,而是四舍五入为无穷大,而不是最大为0(对于负数),这是除法的常用实现方式(至少据我所知)。
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

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

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

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


@IVlad:您将如何结合上述操作来执行除以3的运算?
Paul R

@Paul R-是的,这很难。我已经澄清了答案。
IVlad 2010年

用常数除法不是太难(乘以魔术常数,然后除以2的幂),但是用变量除法有点棘手。
Paul R

1
x * 14 == x * 16-x * 2 ==(x << 4)-(x << 2)不应最终成为(x << 4)-(x << 1),因为x < <1是x乘以2吗?
Alex Spencer 2014年

18
  1. 左移1位类似于乘以2。右移类似于除以2。
  2. 您可以添加一个循环来相乘。通过正确选择循环变量和加法变量,可以限制性能。探索了这一点之后,您应该使用农民乘法

9
+1:但左移不仅仅是类似于由2乘以它由2乘以至少直到溢出...
唐·罗比

对于负数,移位除法会产生错误的结果。
大卫

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

那负数呢?我使用eclipse + CDT在-10345上测试了-12345,但结果却不是那么好。
kenmux

您能告诉我为什么要ullDivisor >>= 1while循环之前这样做吗?另外,不会成功nPos >= 0吗?
Vivekanand V

@kenmux您只需要考虑所涉及数字的大小,首先执行算法,然后使用一些适当的决策语句,将正确的符号返回到商/余数!
Vivekanand V

1
@VivekanandV您的意思是添加标志-以后吗?是的,它有效。
kenmux

5

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

与十进制的十进制除法一样,被分红的位数从最高有效位到最低有效位,一次被视为一位。这可以通过二进制除法的左移轻松实现。同样,通过将当前商位左移一个位置,然后附加新的商位来收集商位。

在经典的布置中,这两个左移组合为一个寄存器对的左移。上半部分持有当前余数,下半部分初始持有股利。当被除数位通过左移转移到余数寄存器时,下半部分的未使用的最低有效位用于累加商位。

下面是该算法的x86汇编语言和C实现。移位和加法除法的这种特定变体有时称为“不执行”变体,因为除非当前余数大于或等于除数,否则不执行从当前余数中减去除数的操作。在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

@greybeard感谢您的指导,您是正确的,我将红利与商混合在一起。我会解决的。
njuffa

4

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

以0的结果R开始。

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

现在将B左移一位并重复直到所有位都从A移出为止。

如果将其写出,则更容易看到发生了什么,这是示例:

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

这似乎是最快的,只需要一些额外的编码即可遍历最小数目的位并计算结果。
Hellonearthis 2012年

2

取自这里

这仅用于除法:

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

它基本上是乘以除以基数2

左移= x * 2 ^ y

右移= x / 2 ^ y

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

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


eax不能保存像这样的小数值1/4。(除非您使用定点而不是整数,但未指定整数)
Peter Cordes

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

什么组装的味道?
基思·品森

1
如果您要的是MIPS组装。我想我用MARS编写/运行它。
梅尔西(Melsi)2013年

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

这是什么语法?-(int)multiplyNumber:(int)num1 withNumber:(int)num2
SS Anne

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

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

5
这看起来像python。有人问问题要组装和/或C.
无效
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.