• 通过位运算实现整数的加减乘除


    Table of Contents

    1. 前言
    2. 加法实现
    3. 减法实现
    4. 乘法实现
    5. 除法实现
    6. 求余实现
    7. 结语
    8. 参考链接

    前言

    很久以前就了解过怎样通过位运算实现整数的加减乘除,但是每次都只是大概的了解了一下加减法的实现。

    最近正好有时间就去进一步的了解了乘法和除法的实现,这里一起总结一下。

    加法实现

    对于二进制的整数加法来说,我们需要让 10 的运算结果为 1, 而 00 的运算结果为 0,
    这和 异或 运算的结果刚好相同。

    1 ^ 0 = 1, 0 ^ 0 = 0
    

    同时,我们需要让 11 的运算结果为 10, 也就是需要进位,很明显这无法用单次的位运算操作实现,因此,
    我们可以考虑通过 运算和 移位 运算来实现。

    (1 & 1) << 1 = 10
    

    和竖式加法一样,如果两个数相加不发生进位,那么直接用 异或 就足够了,如果发生进位,
    那么我们就需要将 异或 的结果和 进位 的结果相加。

    C 语言实现:

    int plus(int a, int b) {
      int sum = a, carry = b;
    
      while (carry) {
        int temp = sum;
        sum = temp ^ carry;           // a ^ b
        carry = (temp & carry) << 1;  // (a & b) << 1
      }
    
      return sum;
    }
    

    减法实现

    减去一个数其实就是加上那个数的相反数,我们可以通过如下方法得到一个数的相反数:

    int negate(int num) {
      return plus(~num, 1);
    }
    

    然后,减法就可以很简单的实现了:

    int subtract(int a, int b) {
      return plus(a, negate(b));
    }
    

    乘法实现

    对于乘法来说,直接的实现就是通过很多次的加法来实现,简单粗暴:

    int abs(int num) {
      return num < 0 ? negate(num) : num;
    }
    
    int multiply(int a, int b) {
      int multiplier = abs(a), multiplicand = abs(b);
    
      int product = 0;
      while (multiplicand) {
        product = plus(product, multiplier);
        multiplicand = subtract(multiplicand, 1);
      }
    
      if ((a ^ b) < 0) {
        product = negate(product);
      }
    
      return product;
    }
    

    对于符号位,可以先计算两个数绝对值的乘积,然后根据符号位修改结果。

    但是,我们也可以利用一点数学技巧来进行改进:

    • 当我们的乘数是一个 偶数 的时候,我们可以将被乘数乘 2 而乘数除 2,这不影响结果
    • 当我们的乘数是一个 奇数 的时候,我们可以让积加上一次被乘数,然后让乘数减 1,这同样不影响结果
        int multiply(int a, int b) {
          int multiplier = abs(a), multiplicand = abs(b);
        
          int product = 0;
          while (multiplicand) {
            if (multiplicand & 0x1) {  // 奇数的最低位为 1
              product = plus(product, multiplier);
              multiplicand = subtract(multiplicand, 1);
            } else {
              multiplier = multiplier << 1;      // multiplier * 2;
              multiplicand = multiplicand >> 1;  // multiplicand / 2
            }
          }
        
          if ((a ^ b) < 0) {
            product = negate(product);
          }
        
          return product;
        }
    

    然后我们在观察一下,当乘数是奇数的时候,我们的操作会是:

    • 乘数减 1,变成偶数
    • 乘数除 2

    这和除 2 并向下取整的结果是一样的,对于奇数,右移位的效果和向下取整相同,因此。我们的代码可以修改为:

    int multiply(int a, int b) {
      int multiplier = abs(a), multiplicand = abs(b);
    
      int product = 0;
      while (multiplicand) {
        if (multiplicand & 0x1) {
          product = plus(product, multiplier);
        }
        multiplier = multiplier << 1;
        multiplicand = multiplicand >> 1;
      }
    
      if ((a ^ b) < 0) {
        product = negate(product);
      }
    
      return product;
    }
    

    除法实现

    和乘法一样,我们可以通过不断的减法来实现除法,但是,同样的,我们可以借助数学技巧来获得更好的实现。

    首先我们来看一下以下两个数的除法:

                    1
            -----------------------------
    1 0 1 0 | 1 0 1 0 0 0 1 1
            | 1 0 1 0
    

    当我们像这样进行除法计算的时候,我们的下一步应该是:

                    1
            -----------------------------
    1 0 1 0 | 1 0 1 0 0 0 1 1
            | 1 0 1 0
              ---------------------------
              0 0 0 0
    

    此时,我们进行了一次减法,我们减去的是什么数字呢?是 1010 吗?很明显,不是的,而是:

                    1
            -----------------------------
    1 0 1 0 | 1 0 1 0 0 0 1 1
            | 1 0 1 0 0 0 0 0
              ---------------------------
              0 0 0 0 0 0 1 1
    

    这相当与将 1010 向左移了 4 位,我们在更换数字尝试一下:

                      1
            -----------------------------
    1 0 1 1 | 1 0 1 0 1 0 1 1
            |   1 0 1 1 0 0 0
              ---------------------------
                1 0 1 0 0 1 1
    

    很明显,这相当于将数字 1011 向左移了 3 位,为什么不移 4 位呢?因为如果移 4 位,得到的除数就比被除数大了。

    由此,我们可以归纳出除法需要进行的步骤:

    • 首先将除数和被除数进行对齐,即除数和被除数的第一个 1 在同一位上
    • 判断除数是否大于等于被除数,如果为否,就不断右移除数,直到为真
    • 用除数减去当前的被除数,减法的结果作为新的被除数
    • 重复前面的步骤,直到被除数为 0

    然后,我们就可以尝试实现了:

    // 计算整数 a 的有效位长度
    int bitlength(int a) {
      int length = 0;
      while (a) {
        plus(length, 1);
        a = a >> 1;
      }
      return length;
    }
    
    // 计算整数 a 和 b 的有效位长度的差值
    int lengthdiff(int a, int b) {
      return subtract(bitlength(a), bitlength(b));
    }
    
    int division(int a, int b) {
      int dividend = abs(a), divisor = abs(b);
    
      int quotient = 0;
      for (int i = lengthdiff(dividend, divisor); i >= 0; i = subtract(i, 1)) {
        int r = (divisor << i);
        // Left shift divisor until it's smaller than dividend
        if (r <= dividend) {
          quotient |= (1 << i);
          dividend = subtract(dividend, r);
        }
      }
    
      if ((a ^ b) < 0) {
        quotient = negate(quotient);
      }
    
      return quotient;
    }
    

    求余实现

    实现了除法,求余也就差不多,直接把最后剩余的被除数返回就可以了:

    int remain(int a, int b) {
      int dividend = abs(a), divisor = abs(b);
    
      int quotient = 0;
      for (int i = lengthdiff(dividend, divisor); i >= 0; i = subtract(i, 1)) {
        int r = (divisor << i);
        // Left shift divisor until it's smaller than dividend
        if (r <= dividend) {
          dividend = subtract(dividend, (int) r);
        }
      }
    
      if (a < 0) {
        dividend = negate(dividend);
      }
    
      return dividend;
    }
    

    结语

    这里尝试了通过位运算实现整数的四则运算,假如你有兴趣的话,可以试一下浮点数的 @_@

    获取浮点数的二进制表示:

    unsigned float2binary(float x) {
      return ((unsigned*)&x)[0];
    }
    

    完整的代码链接:Incremental addition, subtraction, multiplication and division of integers by bit operations

    参考链接

  • 相关阅读:
    理解SQL查询的底层原理
    Android手机里的垃圾文件和文件夹清理
    从树莓派开始玩电脑
    Oracle 游标使用全解
    oracle获得当前时间,精确到毫秒并指定精确位数
    Oracle存储过程创建及调用
    windows命令行(DOS批处理)添加任务计划
    JS计算字符串的长度
    皮肤和DLL和图片等项目文件完全整合到exe中
    C#中使用Socket实现简单Web服务器
  • 原文地址:https://www.cnblogs.com/rgbit/p/10365840.html
Copyright © 2020-2023  润新知