likes
comments
collection
share

Java语言下,位运算符理论重点弄明白<<、>>、>>>三个操作符到底是怎么操作的,以及对计算机关于原码、反码、补码的基

作者站长头像
站长
· 阅读数 86

在位运算中,有三个操作符是相对难以理解的,这篇文章咱们就针对<<、>>、>>>这三个操作符一探究竟,弄清楚它们到底是什么操作的;开始之前,需要补充一点计算机关于原码、反码、补码的基础知识!这部分已经有大牛说的很清楚了

我们直接通过例子说明的方式。

1.左移<<

符号位不变,右边补0。

正数

byte num = 4
原码 0000 0100 
反码 0000 0100 
补码 0000 0100
num<<1左移一位
补码 0000 1000
反码 0000 1000
原码 0000 1000
num = 8

因为正数原码、反码、补码是一样,所以上述操作可以简化。
原码 0000 0100
num<<1左移一位
原码 0000 1000
num = 8

可以发现,正数的左移就是数*2的N次方(在不溢出的情况下);例子:4>>1->4*2 = 8。

负数

byte num = -4
原码 1000 0100 符号为1(负数)
反码 1111 1011 进行反码时,符号位也是不变的
补码 1111 1100 同样符号位是不变的,溢出也不会改变符号位。
num<<1左移一位
补码 1111 1000 对补码进行位移符号位也是不参与移动
反码 1111 0111 反向的反码就是对位移后的补码减1
原码 1000 1000
num = 8

同样,负数的右移也是数*2的N次方(在不溢出的情况下);例子:-4>>1->-4*2 = -8。

2.右移>>

符号位不变,左边补符号值。

正数

byte num = 4;
直接简化操作
原码 0000 0100
num>>1进行右移一位
原码 0000 0010
num = 2

可以发现,正数右移就是数/2的N次方;例子:9>>1 ->(9-1)/2 = 4 。

byte num = 9
原码 0000 1001
num>>1进行右移一位
原码 0000 0100
num = 4

负数

byte num = -4
原码 1000 0100 符号为1(负数)
反码 1111 1011 进行反码时,符号位也是不变的
补码 1111 1100 同样符号位是不变的,溢出也不会改变符号位。
num>>1进行右移一位
补码 1111 1110 对补码进行位移符号位也是不参与移动
反码 1111 1101 反向的反码就是对位移后的补码减1
源码 1000 0010 
num = -2

负数的右移就必须进行原码-反码-补码-运算后(补码)-反码-原码的转换过程。

另外,负数右移就是(-数-1)/2的N次方;例子:-9>>1 -> (-9-1)/2 = -5 。

byte num = -9
原码 1000 1001
反码 1111 0110
补码 1111 0111
num>>1进行右移一位
补码 1111 1011
反码 1111 1010
原码 1000 0101
num = -5

思考一个问题,为什么正数9右移一位是4,而负数-9右移一位是-5;数值不对等呢?再看下面例子:

byte num = -10
原码 1000 1010
反码 1111 0101
补码 1111 0110
num>>1进行右移一位
补码 1111 1011
反码 1111 1010
原码 1000 0101
num = -5

仔细观察-10和-9两位数在>>1之后的补码是完全一样的,我想原因就出在-9的补码后在右移一位,直接把补码移调了,相当于变向-1了,从而导致-9位移之后的补码和-10的相等,那-9位移的之后的值自然也和-10一样,同理正数9右移为啥(9-1)/2 = 4。所以综上推导出,奇数(右移就是(数-1)/2的N次方)的结论。

3.无符号右移>>>

与>>区别就是首位不再是符号位,所以会直接把原本的符号位直接往右位移了,左边补0。

正数

byte num = 4
直接简化操作
原码 0000 0100
num>>>1进行无符号右移一位
原码 0000 0010
num = 2

正数的无符号右移跟有符号右移是一样的。

负数

重点关注一下负数的无符号右移。

byte num = -4
原码 1000 0100
反码 1111 1011
补码 1111 1100
num>>>1进行无符号右移一位
补码 0111 1110 因为没有符号位,所以原本的符号位直接当做数值往右移动了。
反码 0111 1110
原码 0111 1110 因为变成了正数,所以原码、反码、原码一样。
num = 126

但是这里要格外注意,你实际去用Java代码打印时可能会不一样。

byte num  = -4;
num = (byte) (num>>>1);
System.out.println(num);

得到的结果是
num = -2

结果确不一样,为什么会这样呢?因为在jvm底层位运算都是对int值的操作,所以首先会把byte转换成int,所以过程就变成了下面这样。

byte num = -4
jvm操作扩容
int num = -4
原码 10000000 00000000 00000000 00000100
反码 11111111 11111111 11111111 11111011
补码 11111111 11111111 11111111 11111100
num>>>1进行无符号右移一位
补码 01111111 11111111 11111111 11111110
反码 01111111 11111111 11111111 11111110
补码 01111111 11111111 11111111 11111110 因为变成了正数,所以原码、反码、原码一样。

然后回到(byte) (num>>>1)这一步,进行了数据类型强转,就变成了11111110。
又变成了负数,那需要转会原码。
补码 11111110
反码 11111101
原码 10000010
num = -2

这就解释了上面在java程序上执行-4>>>1为啥结果是-2,所以运用其它运算符在进行操作时,也格外需要注意数据类型转换带来的结果差异问题,同时有一个小点Integer.toBinaryString()此方法打印的是补码。

4.总结

  1. 二进制的最高位是符号位:0表示正数,1表示负数;
  2. 正数的原码、反码、补码都一样;
  3. 负数的反码 = 它的原码符号位不变,其他位取反(0 ->1 ; 1->0 );
  4. 负数的补码 = 它的反码 +1;
  5. 0的反码、补码都是0,计算机中0是正数;
  6. 在计算机运算的时候,都是以补码的方式来运算的;

5.口诀

  1. 左移乘;
  2. 右移偶数除,奇数减一再除;
  3. 无符号右移正数同右移,负数会变正;
转载自:https://juejin.cn/post/7012911643176730661
评论
请登录