我一直在使用int mid = low + ((high - low) / 2);
计算中点,因为这可以防止潜在的溢出。这是有道理的,因为(high - low) / 2
确保没有溢出。然而最近我发现了一种更快的方法。这是int mid = (low + high) >>> 1;
。
显然这也不会导致溢出。有人可以解释一下原因吗?这与int mid = (low + high) / 2
非常相似,(low + high)
导致溢出。
System.out.println((Integer.MAX_VALUE + Integer.MAX_VALUE) / 2); // -1
System.out.println((Integer.MAX_VALUE + Integer.MAX_VALUE) >>> 1); //2147483647
System.out.println((Integer.MAX_VALUE + (Integer.MAX_VALUE - Integer.MAX_VALUE) / 2)); //2147483647
答案 0 :(得分:2)
通过有效地将您可以表示的最大数量加倍来防止溢出。与>>
不同,>>>
不会将符号位视为特殊符号,因此就此操作而言,您可以使用全部32位来表示数字。
为了简化示例,让我们设想一个3字节的带符号类型,使用两个补码,就像Java对其整数类型所做的那样。
0b000 = 0
0b001 = 1
0b010 = 2
0b011 = 3 // MAX
0b100 = -4 // MIN
0b101 = -3
0b110 = -2
0b111 = -1
在二进制补码中,如果最左边的位为零,则将该数字解释为正数。如果最左边的位是1,那么它是负的;反转这些位并加一个来获得幅度。
0b101
= -(0b010 + 1)
= -(2 + 1)
= -3
...并且具有理想的属性,将011
增加到100
以尽可能合理的方式溢出 - MAX
到MIN
,并递减{ {1}} 000
111
会为您提供-1
。
现在,如果我们在这个方案中添加3 + 2,我们会溢出到底片:
0b011 + 0b010 = 0b101
如果我们将其解释为无符号3位整数,则会转换为5(正确)。但由于Java将整数解释为两个补码,因此它转换为-3。
-3 / 2
给出-1 - 不是我们想要的答案。
-3 >>> 1
为0b101 >>> 1
提供0b010
,即2,即我们想要的答案。
所以在这里我们使用>>> 1
作为"除以2,将原始数字视为无符号整数"。当然,你只能用它来除以2的幂。
在Java 8中,java.lang.Integer
和java.lang.Long
中有许多新方法可用于将数字视为无符号。一个是divideUnsigned()
,可用于除以任意数字:
返回将第一个参数除以第二个参数的无符号商,其中每个参数和结果都被解释为无符号值。
对于您的代码的未来读者来说,优先使用>>>
可能会有所帮助。
通过使用无符号安全方法处理整数,您可以处理大于Integer.MAX_VALUE
- 2^31 -1
的数字。但是,如果超过2^32 -1
,您仍然可能会溢出。
最大的Java数组或List
可以是Integer.MAX_VALUE
,因此对于数组或列表索引,(low + high) >>> 1
或Integer.divideUnsigned( low + high, 2)
是安全的。
答案 1 :(得分:1)
取决于你认为“溢出证据”。 >>>运算符本身与溢出无关,它的表达式“低+高”可以溢出。
然而,在实践中,低/高将是列表或数组索引(读取:正整数),这会产生重大影响。
虽然添加低+高可以超过Integer.MAX_VALUE(这是溢出),但是“>>> 1”将参数视为无符号 - 因此可能溢出并导致符号翻转的位被正确恢复转移usigned,将结果返回到signed int的范围内。