zoukankan      html  css  js  c++  java
  • 位运算实现加减乘除四则运算(Java)

    【本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究。若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!】

    本文是继《一文了解有趣的位运算》的第二篇文章.

    我们知道,计算机最基本的操作单元是字节(byte),一个字节由8个位(bit)组成,一个位只能存储一个0或1,其实也就是高低电平。无论多么复杂的逻辑、庞大的数据、酷炫的界面,最终体现在计算机最底层都只是对0101的存储和运算。因此,了解位运算有助于提升我们对计算机底层操作原理的理解。

    一、加法

    两个二进制数异或运算的结果是不考虑进位时的结果,

    两个二进制数与运算的结果中含有1的位是有进位的位。

    0101 + 0001 = 0110为例分析如下:

    //计算 0101 + 0001
    0101 ^ 0001 = 0100 //异或结果表明,如果不考虑进位,那么结果为0100
    0101 & 0001 = 0001 //与运算结果表明,最低位需要向次低位进1
    0001 << 1 = 0010   //与运算结果左移一位,将进位加到高位上
    
    //递归计算 0100 + 0010,直到+号右侧数字为0
    

    java代码:

    递归

    public static int add(int a, int b) {
       if (b == 0) {
           return a;
       } else {
           return add(a ^ b, (a & b) << 1);
       }
    }
    
    

    循环

    public static int add2(int a, int b) {
        int sum = a;
        while (b != 0) {
            sum = a ^ b;
            b = (a & b) << 1;
            a = sum;
        }
        return sum;
    }
    
    

    二、减法

    与加法的思路一致,只不过减去一个数等于加一个数的相反数。

    例如:5-1 = 5+(-1)。所以,我们只需要求出被减数的相反数即可。

    如何求出一个数的相反数?

    计算机中存储的是二进制的补码形式,正数的补码与原码相同,负数的补码为对该数的原码除符号位外各位取反,然后在最后一位加1。

    例如:

    1在计算机中的二进制表示为:0000 0001

    -1在计算机中的二进制表示为:1111 1111

    计算过程为:

    -1的原码:1000 0001

    -1的反码:1111 1110

    -1的补码:1111 1111

    其中,由1的原码(0000 0001)取反可得-1的反码(1111 1110)

    总结,一个数的相反数的求法就是该数每一位取反,末位加一。

    java代码:

    public static int minus(int a, int b) {
        return add(a, add(~b, 1));
    }
    

    三、乘法

    如果没有思路,可以先在纸上笔算二进制乘法的过程:

    0101    a
    0110    b
    ------------
    0000    
    101   
    01      
    0       
    ------------
    11110
    

    梳理下笔算二进制乘法的过程:

    初始化乘积结果为0,依次遍历数字b的末位 0→1→1→0,当末位为0时,乘积结果加上0,也就是乘积不变,A左移一位;当末位为1时,乘积结果加上A,A再左移一位。

    如何遍历数字b的末位呢?

    根据前面所学,我们可以使用与运算取一个数的末位,并不断右移数字b,直到数字b==0,即可结束位移。

    需要注意的是正负数的符号问题,此处是先对a、b两数的绝对值计算其乘积,最后再确定其符号。

    java代码为:

    public static int multiply(int a, int b) {
          //将乘数和被乘数都取绝对值
          int A = a < 0 ? add(~a, 1) : a;
          int B = b < 0 ? add(~b, 1) : b;
          //计算绝对值的乘积
          int P = 0;
          while (B != 0) {
              if ((B & 1) != 0) { //取乘数的二进制的最后一位,0 or 1
                  P = add(P, A);
              }
              A = A << 1;
              B = B >> 1;
          }
          //计算乘积的符号
          if ((a ^ b) < 0) {
              P = add(~P, 1);
          }
          return P;
    }
    

    四、除法

    【本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究。若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!】

    最简单的除法实现就是不停的用除数去减被除数,直到被除数小于除数时,此时所减的次数就是我们需要的商,而此时的被除数就是余数。

    唯一需要注意的就是商的符号和余数的符号。商的符号确定方式也乘法是一样,即同号为正,异号为负。而余数的符号和被除数的符号是一样的。

    和简单的乘法实现一样,这里我们要先对两数的绝对值求商,求余数。最后再确定符号。

    public static int[] divide(int a, int b) {
          //对被除数和除数取绝对值
          int A = a < 0 ? add(~a, 1) : a;
          int B = b < 0 ? add(~b, 1) : b;
          //对被除数和除数的绝对值求商
          int C = A; // 余数C
          int N = 0; // 商N
          while (C >= B) {
              C = minus(C, B); // C-B
              N = add(N, 1); // N+1
          }
          // 求商的符号
          if ((a ^ b) < 0) {
              N = add(~N, 1);
          }
          // 求余数的符合
          if (a < 0) {
              C = add(~C, 1);
          }
          return new int[]{N, C};
    }
    

    需要指出的是,这种算法在A很大、B很小的情况下效率很低,那该如何优化算法减少while循环的次数呢?

    不难想到,除法是由乘法的过程逆推而来的。例如 9÷4=2...1,也就是2*4+1=9。假设用9去减4*2,可以得出结果等于1,因为1小于4,那么就可以得出9÷4的商是2,余数是1。

    如何确定4的倍数是逼近最终结果的关键。我们知道,int 整型有32位,除首位表示符号位,每一位的大小是 [2^0, 2^1, 2^2, , , 230],最大的int整数是231-1。所以,我们可以依次将被除数与2^31, 2^30, ...2^3, 2^2, 2^1, 1相乘,如果除数大于它们的乘积,除数就与之相减,并用相减得到的余数继续作为除数,直到循环结束。

    java代码:

    public static int[] divide(int a, int b) {
        // 对被除数和除数取绝对值
        int A = a < 0 ? add(~a, 1) : a;
        int B = b < 0 ? add(~b, 1) : b;
    
        int N = 0; // 商 N
        for (int i = 31; i >= 0; i--) {
            // 未使用A>=(B<<i)进行判断,因为只有左移B时舍弃的高位不包含1,才相当于该数乘以2的i次方.
            if ((A >> i) >= B) { // A ÷ 2^i >= B
                N += (1 << i); // N = N + 2^i
                A -= (B << i); // A = A - B*2^i
            }
        }
    
        int C = A; // 余数C
        // 求商的符号
        if ((a ^ b) < 0) {
            N = add(~N, 1);
        }
        // 求余数的符号
        if (a < 0) {
            C = add(~C, 1);
        }
        return new int[]{N, C};
    }
    

    版权声明

    【本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究。若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!】

  • 相关阅读:
    PHP导出sql文件
    BugScan插件编写高(gǎo)级(jī)教程
    php父级目录文件包函问题
    检测web服务器指定位置大文件是否存在
    解决Linux关闭SSH,终端后运行程序终止问题(包括后台)
    Python Matplotlib绘图库 安装
    校园网突围之路由器开wifi__windows版
    [openwrt 项目开发笔记]: 传送门
    [Openwrt 项目开发笔记]:PHP+Nginx安装(七)
    [Openwrt 项目开发笔记]:MySQL配置(六)
  • 原文地址:https://www.cnblogs.com/onblog/p/13036279.html
Copyright © 2011-2022 走看看