大数据Java基础——移位运算的真实剖析 (一)

来源:互联网 发布:外包软件测试公司 编辑:程序博客网 时间:2024/06/07 17:55

  抛砖引玉:

  Java 中定义了 3 种移位运算符,分别是左移运算符“<<”、右移运算符“>>”和无符号右移运算符“>>>”,对于移位运算,移位运算两边的操作数要求为整型,即 byte、short、char、 int 和 long类型,或者通过拆箱转换后为整型。当操作数的类型为 byte、short 或 char 类型时, 会自动提升为 int 类型,运算的结果也为 int类型。对于移位运算,人们对其“误会”实在太深了……

  超过自身位数的移位

  我们知道,int 类型占用 4 字节,32 位,而 long 类型占用 8 字节,64 位。那么,如果将 int 类型(long 类型)移动超过 31位(63 位)便失去了意义,因为用通俗的话来说,就是“全移 走了”。不过幸运的是,当左侧操作数为 int 类型(byte、char 与 short类型自动提升为 int 类型) 或 long 类型时,如果右侧操作数大于 31 或 63,系统做了相关处理。

  是怎么处理的呢?普遍都是这样认为的:如果左侧操作数为 int 类型(包括提升后为 int 类 型),会对右侧操作数进行除数为 32 的求余运算,如果左侧操作数为long 类型,会对右侧操作 数进行除数为64的求余运算,例如: 

int i = 20;  int j = 30;  i = i << 3;  j = j >> 70;

  结果会先进行求余运算:

3 % 32 //结果为3  70 % 64 //结果为6

  因此,实际右侧的操作数是 3 与 6,而不是 3 与 70。

  90%的 Java 程序员都持上述观点,然而,遗憾的是,这个想法是不正确的。

  需要注意的是:右侧的操作数可以是任意的整型数值,只要该值没有超过类型变量的取值 范围就可以。那么,当右侧操作数为负值时,例如:

int i= 28;  i = 28 << -4;

  按照上面的观点,首先对 32 求余,即:

 -4 % 32

  结果还是−4,现在问题来了,向左移动−4 位,该怎样移动?

  实际上,当左侧操作数为 int 类型时(包括提升后为 int 类型),右侧操作数只有低 5位是 有效的(低 5 位的范围为 0~31) ,也就是说可以看作右侧操作数会先与掩码 0x1f 做与运算(&) ,然后左侧操作数再移动相应的位数。类似地,当左侧操作数为 long 类型时,右侧操作数只有低 6 位是有效的,可以看作右侧操作数先与掩码 0x3f做与运算,然后再移动相应的位数。例如, 假设有如下的赋值运算:

 int i = 5 << -10;  −10 的补码为: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 0

  取其低 5 位,结果为: 1 0 1 1 0 这个值就是 22,也就是相当于:

  int i = 5 << 22;

  因此,不要把移位运算右侧的操作数与求余运算联系在一起,那是不正确的。

  移位运算与乘除运算

  由于数据采用二进制来表示,因此就会普遍存在这样的想法:左移一位就相当于乘以 2, 而右移一位就相当于除以 2,这种想法正确吗?下面的程序将给予验证。

  【例 】移位与乘除

1. package chapter2;  2.  3. public class ShiftOperation {  4. public static void main(String[] args) {  5. testShift(10); //正偶数  6. testShift(-10); //负偶数  7. testShift(0); //0  8. testShift(9); //正奇数  9. testShift(-9); //负奇数  10. }  11.  12. public static void testShift(int value) {  13. int multiply = value * 2;  14. int divide = value / 2;  15. int leftShift = value << 1;  16. int rightShift = value >> 1;  17. System.out.println(value + "*2=" + multiply);  18. System.out.println(value + "/2=" + divide);  19. System.out.println(value + "<<1=" + leftShift);  20. System.out.println(value + ">>1=" + rightShift);  21. System.out.print("测试结果:" + value + "*2="+ value + "<<1"); 22. if (multiply == leftShift) {  23. System.out.println("通过!");  24. } else {  25. System.out.println("不通过!");  26. }  27. System.out.print("测试结果:" + value + "/2="+ value + ">>1"); 28. if (divide == rightShift) {  29. System.out.println("通过!");  30. } else {  31. System.out.println("不通过!");  32. }  33. }  34.

  本程序选取了几个数值,然后分别计算该数值乘以 2、除以 2、左移一位与右移一位的值(第 13~16 行),接下来比较乘以 2 与左移一位(第22~26 行)、除以 2 与右移一位(第 28~32 行) 是否相等。程序运行结果如下:

10*2=20  10/2=5  10<<1=20  10>>1=5  测试结果:10*2=10<<1通过!  测试结果:10/2=10>>1通过!  -10*2=-20  -10/2=-5  -10<<1=-20  -10>>1=-5  测试结果:-10*2=-10<<1通过!  测试结果:-10/2=-10>>1通过!  0*2=0  0/2=0  0<<1=0  0>>1=0  测试结果:0*2=0<<1通过!  测试结果:0/2=0>>1通过!  9*2=18  9/2=4  9<<1=18  9>>1=4  测试结果:9*2=9<<1通过!  测试结果:9/2=9>>1通过!  -9*2=-18  -9/2=-4  -9<<1=-18  -9>>1=-5  测试结果:-9*2=-9<<1通过!  测试结果:-9/2=-9>>1不通过!

  输出结果除了最后一行以外,其余都是相等的。那么最后一行有什么特别吗? 这要从相除的舍入模式说起。在 Java中,当两个操作数都是整型的时候,结果也是整型的 (假设没有发生 ArithmeticException 异常)。如果不能整除,则结果是向 0 舍入的,也就是说,

  向靠近 0 的方向取值。例如在本程序中,表达式:

 9 / 2

  的值为 4.5,介于 4 与 5 之间,由于 4 更靠近 0,所以向 0 舍入,结果为 4,这相当于向下舍入, 而表达式:

 -9 / 2

  的值为−4.5,介于−4 与−5 之间,向 0 舍入为−4,这相当于向上舍入。而对于移位运算来说,表达式:

 9 >> 1

  的值为 4,相当于向下舍入,而表达式:

 -9 >> 1

  的值为−5,还是相当于向下舍入,因此,不同的数值出现了。由于相乘运算与整除的时候不涉及如上舍入模式的问题,所以,值是相等的。但是,一旦不能整除,就会涉及舍入模式,这样, 结果就会存在差异了。表 2-4 给出了具体的情况。

  所以,乘以 2n与左移 n 位的值是相等的,如果可以整除,除以 2n与右移 n 位的值也是相等 的。如果不能整除,当被除数为正数时,除以 2n与右移n 位的值相等,当被除数为负数时,除 以 2n与右移 n 位的值则是不相等的。

  注意 以上所指的右移,如无特殊说明,均指有符号右移(>>)。

  本文出自柠檬派 ,作者:梁勇http://www.lemonpai.com/1009.html 请务必保留此出处 ,否则将追究法律责任!

  相关阅读:大数据Java基础——移位运算的真实剖析 (二) http://www.lemonpai.com/1023.html 有问题希望大家多多指点。

0 0
原创粉丝点击