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

我们知道,计算机最基本的操作单元是字节(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
       0101
      0101
   + 0000
    ----------------
     00011110

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

初始化乘积结果为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;
}

四、除法

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

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

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

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, , , 2^30],最大的int整数是2^31-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};
}

Java位运算实现加减乘除四则运算的更多相关文章

  1. 用Java位运算实现加减乘除四则运算

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6412875.html 感谢博客:http://blog.csdn.net/itismelzp/article/ ...

  2. Java位运算实现加减乘除

    一.加法 a+b 举例实现:13+9=22 13+9不考虑进位结果为12 只考虑进位结果为10 和刚好是22. 13二进制为1101,9二进制为1001. 不考虑进位结果为0100.算式为a^b 只考 ...

  3. 位运算实现加减乘除四则运算(Java)

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

  4. Java位运算总结:位运算用途广泛《转》

    前天几天研究了下JDK的Collection接口,本来准备接着研究Map接口,可是一查看HashMap类源码傻眼咯,到处是位运算实现,所以我觉得还是有必要先补补位运算知识,不然代码看起来有点费力.今天 ...

  5. Java位运算总结:位运算用途广泛

    前天几天研究了下JDK的Collection接口,本来准备接着研究Map接口,可是一查看HashMap类源码傻眼咯,到处是位运算实现,所以我觉得还是有必要先补补位运算知识,不然代码看起来有点费力.今天 ...

  6. 我们必须要了解的Java位运算(不仅限于Java)

    本文原创地址为 https://www.cnblogs.com/zh94/p/16195373.html 原创声明:作者:陈咬金. 博客地址:https://www.cnblogs.com/zh94/ ...

  7. Java 位运算2-LeetCode 201 Bitwise AND of Numbers Range

    在Java位运算总结-leetcode题目博文中总结了Java提供的按位运算操作符,今天又碰到LeetCode中一道按位操作的题目 Given a range [m, n] where 0 <= ...

  8. Java位运算原理及使用讲解

    前言日常开发中位运算不是很常用,但是巧妙的使用位运算可以大量减少运行开销,优化算法.举个例子,翻转操作比较常见,比如初始值为1,操作一次变为0,再操作一次变为1.可能的做法是使用三木运算符,判断原始值 ...

  9. (转)java位运算

    转自:http://aijuans.iteye.com/blog/1850655 Java 位运算(移位.位与.或.异或.非)   public class Test { public static ...

随机推荐

  1. Python入门基础(10)_异常_1

    最近有点忙,到现在快一个月没写了,罪过罪过,继续学习 异常:python程序在运行时,如果python解释器遇到一个错误,那么程序就会停止执行,并且会提示一些错误信息,这就是异常. 抛出异常:程序停止 ...

  2. Oracle中的字符函数

    Oracle中常用的字符串函数有以下几种: 1.upper()---将字符串的内容全部转换为大写.lower()---将字符串的内容全部转换为小写.具体用法: select  upper('test' ...

  3. 【原创】关于DNS不得不说的一些事

    引言 今天我们来聊聊DNS. 所谓域名系统(Domain Name System缩写DNS,Domain Name被译为域名)是因特网的一项核心服务,它作为可以将域名和IP地址相互映射的一个分布式数据 ...

  4. cython 第一次接触

    https://www.cnblogs.com/kaituorensheng/p/4452881.html

  5. C#编写dll进行sql server数据库扩展储存过程

    一.编写C#函数文件 1.新建一个类库文件 备注:sqlserver 2008只能用.net3.5版本. 2.如有想加入强命名的话可如下步骤: 参考博文:https://blog.csdn.net/d ...

  6. Codeforces 1008C

    题意略. 思路: 其实我们没有必要关注每个数字的位置,我们只要把大的数字放在小的数字上就可以了,这样它的位置必然会发生变换. 在变换时,这个替换的序列越长越好,每个序列对答案的贡献就是该序列的长度 - ...

  7. Python 内存分配时的小秘密

    Python 中的sys 模块极为基础而重要,它主要提供了一些给解释器使用(或由它维护)的变量,以及一些与解释器强交互的函数. 本文将会频繁地使用该模块的getsizeof() 方法,因此,我先简要介 ...

  8. Leetcode之回溯法专题-37. 解数独(Sudoku Solver)

    Leetcode之回溯法专题-37. 解数独(Sudoku Solver) 编写一个程序,通过已填充的空格来解决数独问题. 一个数独的解法需遵循如下规则: 数字 1-9 在每一行只能出现一次.数字 1 ...

  9. qt学习笔记(1):qt点击运行没有反应。

    因为公司的项目需要,今天开始重新学习已经忘干净了的QT, 说起qt之前在学校刚接触的时候就打心底里喜欢这个编辑器, 因为一直使用vs做项目,面对着黑洞洞的窗口总让人不舒服, 自从接触了qt感觉迎来了曙 ...

  10. 基本图像操作和处理(python)

    基本图像操作和处理(python) PIL提供了通用的图像处理功能,以及大量的基本图像操作,如图像缩放.裁剪.旋转.颜色转换等. Matplotlib提供了强大的绘图功能,其下的pylab/pyplo ...