位移操作和求平均值溢出
以前写排序算法时有用到取index平均值的运算,通常的写法是这样的:
mid = lo + (hi - lo) / 2;
这样的写法是为了防止溢出,大家应该都清楚,记得LeetCode上有一道题目专门设置了这个陷阱,相加除2的方法会导致结果错误。
最近在看ArrayList系统源码时,看到了下面这种写法:
mid = (lo + hi) >>> 1;
先复习一下移位操作:
>>> 表示无符号右移,>> 表示有符号右移,右移n位相当于除以2的n次幂。
两者的区别在于右移后,>>> 最高位补0,>> 正数最高位补0负数补1。所以对于正数来讲,两者没有区别,但对于负数应该使用后者。
溢出的情况比较特殊,具体到求平均值的情况,两个int类型正整数相加,即使发生溢出也至多1位,就是说符号位会变成1但不会再产生进位了,因此并不会丢失精度。这个时候我们可以把它看做无符号整数,然后使用无符号右移1位,相当于除以2,高位补0不再溢出,所以结果还是对的。但如果我们使用有符号右移,它会被看成一个负数,最高位补1,导致结果错误。
做一下测试:
int hi = Integer.MAX_VALUE, lo = Integer.MAX_VALUE - 2;
System.out.println((hi + lo) / 2);
System.out.println((hi - lo) / 2 + lo);
System.out.println((hi + lo) >> 1);
System.out.println((hi + lo) >>> 1);
输出结果:
-2
2147483646
-2
2147483646
结果符合预期,所以index取平均值时,使用第二和第四种方式可以避免溢出。