是否溢出,使用>>>操作者

时间:2016-10-25 15:42:42

标签: java binary

我一直在使用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

2 个答案:

答案 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以尽可能合理的方式溢出 - MAXMIN,并递减{ {1}} 000 111会为您提供-1

现在,如果我们在这个方案中添加3 + 2,我们会溢出到底片:

0b011 + 0b010 = 0b101 

如果我们将其解释为无符号3位整数,则会转换为5(正确)。但由于Java将整数解释为两个补码,因此它转换为-3。

-3 / 2给出-1 - 不是我们想要的答案。

-3 >>> 10b101 >>> 1提供0b010,即2,即我们想要的答案。

所以在这里我们使用>>> 1作为"除以2,将原始数字视为无符号整数"。当然,你只能用它来除以2的幂。

在Java 8中,java.lang.Integerjava.lang.Long中有许多新方法可用于将数字视为无符号。一个是divideUnsigned(),可用于除以任意数字:

  

返回将第一个参数除以第二个参数的无符号商,其中每个参数和结果都被解释为无符号值。

对于您的代码的未来读者来说,优先使用>>>可能会有所帮助。

通过使用无符号安全方法处理整数,您可以处理大于Integer.MAX_VALUE - 2^31 -1的数字。但是,如果超过2^32 -1,您仍然可能会溢出。

最大的Java数组或List可以是Integer.MAX_VALUE,因此对于数组或列表索引,(low + high) >>> 1Integer.divideUnsigned( low + high, 2)是安全的。

答案 1 :(得分:1)

取决于你认为“溢出证据”。 >>>运算符本身与溢出无关,它的表达式“低+高”可以溢出。

然而,在实践中,低/高将是列表或数组索引(读​​取:正整数),这会产生重大影响。

虽然添加低+高可以超过Integer.MAX_VALUE(这是溢出),但是“>>> 1”将参数视为无符号 - 因此可能溢出并导致符号翻转的位被正确恢复转移usigned,将结果返回到signed int的范围内。