为什么Integer.MIN_VALUE的绝对值等于它本身?

11
在Java中,当我使用Integer i = Math.abs(Integer.MIN_VALUE)时,我得到的答案与原始值相同,这意味着i包含了Integer.MIN_VALUE。我在C++中进行过验证。
为什么会出现这种行为?
6个回答

13

请阅读 Joshua Bloch 的《Effective Java》。

我找到了这个问题的答案,以下是解释: 计算机使用二进制算术,Java 中的 Math.abs 函数或任何语言中的 absolute 函数的逻辑如下:

if(num >= 0)
    return num;
else
    return (2's complement of the num);

注意:如何找到二进制补码

对于给定的数字,我们首先找到它的一补数,然后再加1。例如: 假设我们的数字为10101, 则其一补数为01010, 二补数为01011(在一补数上加1)。

现在,为了方便和清晰起见,假设我们的整数(有符号)大小为3位,下面是可以使用这四个位生成的可能数字列表:

000 --> 0 (0)
001 --> 1 (1)
010 --> 2 (2)
011 --> 3 (3) 
100 --> 4 (-4)
101 --> 5 (-3)
110 --> 6 (-2)
111 --> 7 (-1)
现在这个被签署了,这意味着一半的数字是负数,而另一半是正数(以第一位为1的数字是负数)。让我们从000开始寻找它的负数,它将是000的二进制补码。
2's complement of `000` = 1 + `111` = `000`
2's complement of `001` = 1 + `110` = `111`
2's complement of `010` = 1 + `101` = `110`
2's complement of `011` = 1 + `100` = `101`
2's complement of `100` = 1 + `011` = `100`
2's complement of `101` = 1 + `010` = `011`
2's complement of `110` = 1 + `001` = `010`  
2's complement of `111` = 1 + `000` = `001`
从上面的演示中,我们发现111(-1)的2补码是001(1),同样110(-2)的2补码是010(2)101(-3)的2补码是011(3)100(-4)的2补码是100(-4),我们可以看到,-4是使用3位表示的最小负数。
这就是为什么Integer.MIN_VALUE的绝对值是Integer.MIN_VALUE的原因。

4
Java 的定义中使用的是二进制补码,但 C++ 实现并不要求使用二进制补码。如果使用一进制补码,则上述内容不适用。需要将原文中的 "compliment" 改为 "complement"。 - C. K. Young
@ChrisJester-Young 感谢您提出编辑建议。将 s/compliment/complement/g 进行更正。虽然有些晚,但仍然值得。 - dharam

6
这种现象的根本原因在于存在固有的不对称性。32位比特模式的数量是偶数,其中一个模式用于表示零。这就留下了奇数个非零值。正数和负数的数量不能相等,因为它们的总和是奇数。
在Java整数中使用的2的补码表示中,负数的数量比正数多一个。除了Integer.MIN_VALUE之外的每个负数都对应着一个既是其相反数又是其绝对值的正数。Integer.MIN_VALUE则没有对应的正整数,Math.abs和取反映射到其本身。

4
这种行为是现代计算机所采用的表示方法的数学结果。
以下是为什么必须这样的非正式证明。
1. N位有符号二进制数表示法具有2^N个可能的值;即一个元素数量为偶数的整数集合。 2. 从集合中删除零。现在,该集合具有奇数个元素。 3. 现在删除所有形如{n,-n}的数字对。每次删除一对数字后,集合仍然包含奇数个元素。 4. 我们现在剩下一个集合,其中包含n在集合中,但-n不在集合中的奇数个整数。由于集合大小是奇数,它不能是空的;即至少有一个具有此属性的数字。
在Java(以及其他实际语言中)指定的表示法中,整数类型有2N-1个负值和2N-1-1个大于零的值。具有奇怪属性的值是MIN_VALUE。这种表示称为二进制补码
严格来说,整数表示不一定需要有这种异常情况:
- 可以有两个零(-0和+0);例如有符号数值补码表示。(这使得证明的第2步无效:现在有2个零要去除。) - 可以排除-2N-1;即使它成为非法值。(这使得证明的第1步无效:初始集合现在具有奇数个值。) - 可以指定整数算术运算,使得(例如)对MIN_VALUE取反会引发异常。例如,Math.abs(Integer.MIN_VALUE)将抛出异常。
然而,所有这些事情都会产生重大的性能影响,特别是现代计算机硬件仅本地支持二进制补码算术。它们还涉及编写可靠整数代码的问题...

3

Math.abs(int)文档说:如果参数为负数,则返回参数的相反数。 JLS 15.15.4. 负一元运算符说:对于所有整数值x,-x等于(~x)+1

-Integer.MIN_VALUE = ~Integer.MIN_VALUE + 1 = ~0x80000000 + 1 = 0x7FFFFFFF + 1 = 0x80000000 = Integer.MIN_VALUE


特别感谢对Java语言规范的具体引用。 - informatik01

2

您可能期望Integer.MIN_VALUE的绝对值为Integer.MAX_VALUE + 1,但是这个值已经超出了原始的int大小。而Integer.MAX_VALUE + 1又会变成Integer.MIN_VALUE。就是这样。


为什么要特别处理将1与Integer.MIN_VALUE相加,而不是其他整数? - dharam
@dharam 这并不是特殊处理,我试图解释 Integer.MIN_VALUE 的绝对值大于 int 类型可以表示的最大整数。 - Juvanis
@dharam 在二进制补码算术中,你可以通过对一个数取反再加1来实现其相反数。在使用二进制补码系统时,Integer.MIN_VALUEInteger.MAX_VALUE刚好是彼此的补码。 - C. K. Young
1
@ChrisJester-Young - 我不确定你的意思是什么(因为你的实际答案看起来是正确的),但 Integer.MIN_VALUEInteger.MAX_VALUE 并不真正互补(除非你只是说它们是最小值和最大值)。由于在二进制补码形式中,负数比正数多表示一个,所以 Integer.MIN_VALUE幅度Integer.MAX_VALUE 大一。 - DaoWen
1
@DaoWen 我的意思是 Integer.MIN_VALUEInteger.MAX_VALUE 是彼此的补码。 - C. K. Young
显示剩余2条评论

1
这是因为二进制补码的工作方式。 Integer.MIN_VALUE 对应着 0x80000000。将其取反(在此情况下为 0x7FFFFFFF),再加上1,是标准的求相反数的方法。但在这种情况下,会发生溢出,导致结果回到了 0x80000000。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接