Java语言下,位运算符理论重点弄明白<<、>>、>>>三个操作符到底是怎么操作的,以及对计算机关于原码、反码、补码的基
在位运算中,有三个操作符是相对难以理解的,这篇文章咱们就针对<<、>>、>>>这三个操作符一探究竟,弄清楚它们到底是什么操作的;开始之前,需要补充一点计算机关于原码、反码、补码的基础知识!这部分已经有大牛说的很清楚了。
我们直接通过例子说明的方式。
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.总结
- 二进制的最高位是符号位:0表示正数,1表示负数;
- 正数的原码、反码、补码都一样;
- 负数的反码 = 它的原码符号位不变,其他位取反(0 ->1 ; 1->0 );
- 负数的补码 = 它的反码 +1;
- 0的反码、补码都是0,计算机中0是正数;
- 在计算机运算的时候,都是以补码的方式来运算的;
5.口诀
- 左移乘;
- 右移偶数除,奇数减一再除;
- 无符号右移正数同右移,负数会变正;
转载自:https://juejin.cn/post/7012911643176730661