使用按位运算符实现除法

Posted

技术标签:

【中文标题】使用按位运算符实现除法【英文标题】:Implement division with bit-wise operator 【发布时间】:2011-07-14 04:23:08 【问题描述】:

如何(不只是除以 2 的幂)?

详细描述一下。

【问题讨论】:

请参阅How can I multiply and divide using only bit shifting and adding?,了解紧凑、高效、非递归的 C 实现。 (以及类似的 x86-asm 实现。) 如果有人在采访中问你这个问题,问他们“这是你每天都在做的事情,实施除法吗”? 【参考方案1】:

进行除法的标准方法是实现二进制长除法。这涉及减法,所以只要您不将其视为不是按位运算,那么这就是您应该做的。 (请注意,您当然可以使用按位逻辑运算非常繁琐地实现减法。)

本质上,如果你在做Q = N/D:

    对齐ND 中最重要的那些。 计算t = (N - D);。 如果(t >= 0),则将Q的最低有效位设置为1,并设置N = t。 左移N 1。 左移Q 1。 转到第 2 步。

根据需要循环获取尽可能多的输出位(包括小数),然后应用最终移位来撤消您在步骤 1 中所做的操作。

【讨论】:

对齐 N 和 D 中最重要的那些是什么意思,我们是否在代码中这样做。 @Time:例如,如果 N=9 和 D=3,那么我们有 N=1001,D=11。所以要做的第一件事就是将 D 左移 2 以使前导与 N 匹配,即您使用 D=1100。 @Foli:如果 t @Programmer:哦,我以为它在第 3 步中是隐含的;如果 t >= 0,则设置 Q 的 lsb 并替换 N,否则也不做。如果您曾经手动进行过长除法,那么该算法应该很熟悉(尝试手动将 1001 除以 0011!)。 @OliverCharlesworth 也许我不明白,我试过 N=7=111 和 D=3=011。我们在 3 位。我必须做 7/3 1) 对齐,所以 N=111 和 D=110 2) t = 7-6 = 1 > 0 3) Q = 001 和 N = t = 001 4) N N = 010 5) Q Q = 010 我想我应该停在这里。您写了“根据需要循环尽可能多的输出位(包括小数)”,所以在我的示例中,您说我必须循环 2 次,因为我的结果是 2 位(商 = 10),但是如果我第二次循环,我会得到错误的结果...所以我必须循环 n-1 次(n 是输出的位数)?【参考方案2】:

由于位操作适用于 0 或 1 位,因此每个位代表 2 的幂,所以如果我有位

1010

该值为 10。

每个位是 2 的幂,所以如果我们将这些位向右移动,我们会除以 2

1010 --> 0101

0101 是 5

所以,一般来说,如果你想除以 2 的某个幂,你需要右移你将 2 提高到的指数,以获得该值

例如,要除以 16,您需要移动 4,因为 2^^4 = 16。

【讨论】:

我不认为 OP 只对除以 2 的幂感兴趣。 奥利是对的!我想除以不是 2 的幂的数字【参考方案3】:

我假设我们正在讨论整数除法。

假设我有两个数字 1502 和 30,我想计算 1502/30。我们就是这样做的:

首先,我们将 30 与 1501 的最高位对齐; 30变成3000。然后比较1501和3000,1501包含3000中的0。然后我们比较1501和300,它包含300中的5,然后比较(1501-5*300)和30。所以最后我们得到5*( 10^1) = 50 作为这个除法的结果。

现在将 1501 和 30 都转换为二进制数字。然后不是将 30 与 (10^x) 相乘以将其与 1501 对齐,而是将 (30) 以 2 个基数乘以 2^n 以对齐。并且2^n可以转化为左移n个位置。

代码如下:

int divide(int a, int b)
    if (b != 0)
        return;

    //To check if a or b are negative.
    bool neg = false;
    if ((a>0 && b<0)||(a<0 && b>0))
        neg = true;

    //Convert to positive
    unsigned int new_a = (a < 0) ? -a : a;
    unsigned int new_b = (b < 0) ? -b : b;

    //Check the largest n such that b >= 2^n, and assign the n to n_pwr
    int n_pwr = 0;
    for (int i = 0; i < 32; i++)
    
        if (((1 << i) & new_b) != 0)
            n_pwr = i;
    

    //So that 'a' could only contain 2^(31-n_pwr) many b's,
    //start from here to try the result
    unsigned int res = 0;
    for (int i = 31 - n_pwr; i >= 0; i--)
        if ((new_b << i) <= new_a)
            res += (1 << i);
            new_a -= (new_b << i);
        
    

    return neg ? -res : res;

没有测试,但你明白了。

【讨论】:

【参考方案4】:

使用位运算符将两个数字相除。

#include <stdio.h>

int remainder, divisor;

int division(int tempdividend, int tempdivisor) 
    int quotient = 1;

    if (tempdivisor == tempdividend) 
        remainder = 0;
        return 1;
     else if (tempdividend < tempdivisor) 
        remainder = tempdividend;
        return 0;
       

    do

        tempdivisor = tempdivisor << 1;
        quotient = quotient << 1;

      while (tempdivisor <= tempdividend);


     /* Call division recursively */
    quotient = quotient + division(tempdividend - tempdivisor, divisor);

    return quotient;
 


int main() 
    int dividend;

    printf ("\nEnter the Dividend: ");
    scanf("%d", &dividend);
    printf("\nEnter the Divisor: ");
    scanf("%d", &divisor);   

    printf("\n%d / %d: quotient = %d", dividend, divisor, division(dividend, divisor));
    printf("\n%d / %d: remainder = %d", dividend, divisor, remainder);
    getch();

【讨论】:

你从哪里接divisor 这是来自scanf("%d", &amp;divisor);的用户输入 只有在执行正常的 while(使用 tempdivisor 我喜欢这个作为起点。但不要忘记负数。 -4 除以 2 不是“0 余数 -4”。仍然为这个概念 +1。【参考方案5】:
int remainder =0;

int division(int dividend, int divisor)

    int quotient = 1;

    int neg = 1;
    if ((dividend>0 &&divisor<0)||(dividend<0 && divisor>0))
        neg = -1;

    // Convert to positive
    unsigned int tempdividend = (dividend < 0) ? -dividend : dividend;
    unsigned int tempdivisor = (divisor < 0) ? -divisor : divisor;

    if (tempdivisor == tempdividend) 
        remainder = 0;
        return 1*neg;
    
    else if (tempdividend < tempdivisor) 
        if (dividend < 0)
            remainder = tempdividend*neg;
        else
            remainder = tempdividend;
        return 0;
    
    while (tempdivisor<<1 <= tempdividend)
    
        tempdivisor = tempdivisor << 1;
        quotient = quotient << 1;
    

    // Call division recursively
    if(dividend < 0)
        quotient = quotient*neg + division(-(tempdividend-tempdivisor), divisor);
    else
        quotient = quotient*neg + division(tempdividend-tempdivisor, divisor);
     return quotient;
 


void main()

    int dividend,divisor;
    char ch = 's';
    while(ch != 'x')
    
        printf ("\nEnter the Dividend: ");
        scanf("%d", &dividend);
        printf("\nEnter the Divisor: ");
        scanf("%d", &divisor);

        printf("\n%d / %d: quotient = %d", dividend, divisor, division(dividend, divisor));
        printf("\n%d / %d: remainder = %d", dividend, divisor, remainder);

        _getch();
    

【讨论】:

我测试过了。它可以处理负除法【参考方案6】:

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

代码

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

【讨论】:

【参考方案7】:

此解决方案完美运行。

#include <stdio.h>

int division(int dividend, int divisor, int origdiv, int * remainder)

    int quotient = 1;

    if (dividend == divisor)
    
        *remainder = 0;
        return 1;
    

    else if (dividend < divisor)
    
        *remainder = dividend;
        return 0;
    

    while (divisor <= dividend)
    
        divisor = divisor << 1;
        quotient = quotient << 1;
    

    if (dividend < divisor)
    
        divisor >>= 1;
        quotient >>= 1;
    

    quotient = quotient + division(dividend - divisor, origdiv, origdiv, remainder);

    return quotient;


int main()

    int n = 377;
    int d = 7;
    int rem = 0;

    printf("Quotient : %d\n", division(n, d, d, &rem));
    printf("Remainder: %d\n", rem);

    return 0;

【讨论】:

【参考方案8】:

对于整数:

public class Division 

    public static void main(String[] args) 
        System.out.println("Division: " + divide(100, 9));
    

    public static int divide(int num, int divisor) 
        int sign = 1;
        if((num > 0 && divisor < 0) || (num < 0 && divisor > 0))
            sign = -1;

        return divide(Math.abs(num), Math.abs(divisor), Math.abs(divisor)) * sign;
    

    public static int divide(int num, int divisor, int sum) 
        if (sum > num) 
            return 0;
        

        return 1 + divide(num, divisor, sum + divisor);
    

【讨论】:

这不会处理溢出问题。如果假设整数为 32 位,我的红利是 -2^31 怎么办?【参考方案9】:

所有这些解决方案都太长了。基本思想是将商(例如 5=101)写为 100 + 00 + 1 = 101。

public static Point divide(int a, int b) 

    if (a < b)
        return new Point(0,a);
    if (a == b)
        return new Point(1,0);
    int q = b;
    int c = 1;
    while (q<<1 < a) 
        q <<= 1;
        c <<= 1;
    
    Point r = divide(a-q, b);
    return new Point(c + r.x, r.y);



public static class Point 
    int x;
    int y;

    public Point(int x, int y) 
        this.x = x;
        this.y = y;
    

    public int compare(Point b) 
        if (b.x - x != 0) 
            return x - b.x;
         else 
            return y - b.y;
        
    

    @Override
    public String toString() 
        return " (" + x + " " + y + ") ";
    

【讨论】:

【参考方案10】:

根据关于 C 的移位行为的常见警告,这应该适用于无符号数量,而不管 int 的本机大小...

static unsigned int udiv(unsigned int a, unsigned int b) 
  unsigned int c = 1, result = 0;

  if (b == 0) return (unsigned int)-1 /*infinity*/;

  while (((int)b > 0) && (b < a))  b = b<<1; c = c<<1; 

  do 
    if (a >= b)  a -= b; result += c; 
    b = b>>1; c = c>>1;
   while (c);

  return result;

【讨论】:

【参考方案11】:

在没有除法运算符的情况下实现除法: 您将需要包括减法。但这就像你用手做的一样(仅在 2 的基础上)。附加的代码提供了一个简短的函数来完成这个。

uint32_t udiv32(uint32_t n, uint32_t d) 
    // n is dividend, d is divisor
    // store the result in q: q = n / d
    uint32_t q = 0;

    // as long as the divisor fits into the remainder there is something to do
    while (n >= d) 
        uint32_t i = 0, d_t = d;
        // determine to which power of two the divisor still fits the dividend
        //
        // i.e.: we intend to subtract the divisor multiplied by powers of two
        // which in turn gives us a one in the binary representation 
        // of the result
        while (n >= (d_t << 1) && ++i)
            d_t <<= 1;
        // set the corresponding bit in the result
        q |= 1 << i;
        // subtract the multiple of the divisor to be left with the remainder
        n -= d_t;
        // repeat until the divisor does not fit into the remainder anymore
    
    return q;

【讨论】:

【参考方案12】:

这是我只用按位运算实现除法的解决方案:

int align(int a, int b) 
  while (b < a) b <<= 1;
  return b;


int divide(int a, int b) 
  int temp = b;
  int result = 0;
  b = align(a, b);
  do 
    result <<= 1;
    if (a >= b) 
      // sub(a,b) is a self-defined bitwise function for a minus b
      a = sub(a,b);
      result = result | 1;
    
    b >>= 1;
   while (b >= temp);
  return result;

【讨论】:

【参考方案13】:

无符号长除法 (javascript) - 基于 Wikipedia 文章:https://en.wikipedia.org/wiki/Division_algorithm: “长除法是用于纸笔除以十进制表示的多位数字的标准算法。它从被除数的左端逐渐移动到右端,减去除数的最大可能倍数(在数字级别)在每个阶段;倍数然后成为商的数字,最后的差就是余数。 当与二进制基数一起使用时,此方法构成了以下带余数算法的(无符号)整数除法的基础。"

最后的函数 divideWithoutDivision 将其包装以允许负操作数。我用它解决了leetcode问题“除自我之外的数组乘积”

function longDivision(N, D) 
    let Q = 0; //quotient and remainder
    let R = 0;
    let n = mostSignificantBitIn(N);
    for (let i = n; i >= 0; i--)  
        R = R << 1;
        R = setBit(R, 0, getBit(N, i));
        if (R >= D) 
            R = R - D;
            Q = setBit(Q, i, 1);
        
    
    //return [Q, R];
    return Q;

function mostSignificantBitIn(N) 
    for (let i = 31; i >= 0; i--) 
        if (N & (1 << i))
            return i ;
    
    return 0;

function getBit(N, i) 
    return (N & (1 << i)) >> i;

function setBit(N, i, value) 
    return N | (value << i);

function divideWithoutDivision(dividend, divisor) 
    let negativeResult = (dividend < 0) ^ (divisor < 0);
    dividend = Math.abs(dividend);
    divisor = Math.abs(divisor);
    let quotient = longDivision(dividend, divisor);
    return negativeResult ? -quotient : quotient;

【讨论】:

用文本或 cmets 解释你的答案会更有帮助,这样提问者和其他人就能理解你的逻辑。 谢谢@glycoaddict:添加了描述。

以上是关于使用按位运算符实现除法的主要内容,如果未能解决你的问题,请参考以下文章

C语言的位运算符和逻辑运算符

位运算符详解

运算符优先级

完美实现按位 NOT (~) 运算符(翻转位)

使用 .NET 中的 Interlocked 类实现按位运算

请问怎样用加法-移位实现定点乘除法?