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)所有,若是转载请务必保留本段原创声明,违者必究。若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!】

  • 相关阅读:
    《.NET内存管理宝典 》(Pro .NET Memory Management) 阅读指南
    《.NET内存管理宝典 》(Pro .NET Memory Management) 阅读指南
    《.NET内存管理宝典 》(Pro .NET Memory Management) 阅读指南
    使用Jasmine和karma对传统js进行单元测试
    《.NET内存管理宝典 》(Pro .NET Memory Management) 阅读指南
    《.NET内存管理宝典 》(Pro .NET Memory Management) 阅读指南
    nginx 基于IP的多虚拟主机配置
    Shiro 框架的MD5加密算法实现原理
    项目实战:Qt+OSG三维点云引擎(支持原点,缩放,单独轴或者组合多轴拽拖旋转,支持导入点云文件)
    实用技巧:阿里云服务器建立公网物联网服务器(解决阿里云服务器端口,公网连接不上的问题)
  • 原文地址:https://www.cnblogs.com/onblog/p/13036279.html
Copyright © 2011-2022 走看看