Math.abs对Integer.Min_VALUE返回错误值

106

这段代码:

System.out.println(Math.abs(Integer.MIN_VALUE));

返回值为-2147483648

它难道不应该返回绝对值2147483648吗?

8个回答

120

Integer.MIN_VALUE表示的是-2147483648,但32位整数能够容纳的最大值是+2147483647。试图在32位int中表示+2147483648将会有效地"循环回滚"到-2147483648。这是因为,在使用带符号整数时,+2147483648-2147483648的二进制补码表示是相同的。然而,+2147483648被认为是超出范围的而不是问题。

如果想要更深入地了解这个问题,您可以查看Wikipedia关于二进制补码的文章


9
没问题低估影响是个问题,很可能会带来问题。就我个人而言,我更喜欢在高级语言中采用异常或动态增长的数字系统。 - Maarten Bodewes

50
您指出的行为确实是违反直觉的。然而,这种行为是由javadoc for Math.abs(int)指定的:

如果参数不是负数,则返回该参数。 如果参数是负数,则返回参数的相反数。

也就是说,Math.abs(int)应该像以下Java代码一样运行:
public static int abs(int x){
    if (x >= 0) {
        return x;
    }
    return -x;
}

也就是说,在负数情况下,-x

根据JLS第15.15.4节-x等于(~x)+1,其中~是按位补码运算符。

为了验证这是否正确,让我们以-1为例。

在Java中,整数值-1可以表示为十六进制的0xFFFFFFFF(用println或任何其他方法检查)。因此,取-(-1)得到:

-(-1) = (~(0xFFFFFFFF)) + 1 = 0x00000000 + 1 = 0x00000001 = 1

所以,它可以工作。

现在让我们尝试使用 Integer.MIN_VALUE 进行测试。知道最小整数可以用 0x80000000 表示,即第一位设置为 1,其余的31位设置为 0,我们有:

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

这就是为什么 Math.abs(Integer.MIN_VALUE) 返回 Integer.MIN_VALUE。另外请注意,0x7FFFFFFFInteger.MAX_VALUE
话虽如此,我们如何避免将来由于这种违反直觉的返回值而出现问题呢?
  • 正如 @Bombe 指出的那样,我们可以在操作前将 int 转换为 long。然而,我们必须做到以下两点:

    • 将它们转换回 int,但这不起作用,因为 Integer.MIN_VALUE == (int) Math.abs((long)Integer.MIN_VALUE)
    • 或者继续使用 long,希望我们永远不会调用 Math.abs(long) 与等于 Long.MIN_VALUE 的值,因为我们还有 Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE
  • 我们可以在任何地方使用 BigInteger,因为 BigInteger.abs() 总是返回正值。这是一个很好的替代方法,虽然比操作原始整数类型慢一些。

  • 我们可以编写自己的 Math.abs(int) 包装器,像这样:

/**
 * Fail-fast wrapper for {@link Math#abs(int)}
 * @param x
 * @return the absolute value of x
 * @throws ArithmeticException when a negative value would have been returned by {@link Math#abs(int)}
 */
public static int abs(int x) throws ArithmeticException {
    if (x == Integer.MIN_VALUE) {
        // fail instead of returning Integer.MAX_VALUE
        // to prevent the occurrence of incorrect results in later computations
        throw new ArithmeticException("Math.abs(Integer.MIN_VALUE)");
    }
    return Math.abs(x);
}
  • 使用整数按位与运算清除高位,确保结果为非负数:int positive = value & Integer.MAX_VALUE(实际上是从Integer.MAX_VALUE溢出到0而不是Integer.MIN_VALUE

最后需要注意的是,这个问题似乎已经存在一段时间了。例如,请参见有关相应findbugs规则的条目


14

以下是Java文档中Math.abs()的说明javadoc

请注意,如果参数等于Integer.MIN_VALUE(即可表示的最小负整数值),则结果为该值本身,即为负数。


8
为了看到您期望的结果,请将Integer.MIN_VALUE转换为long
System.out.println(Math.abs((long) Integer.MIN_VALUE));

1
一个可能的修复方案,确实!但是这并不能解决 Math.abs 返回负数的反直觉问题:Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE - bernard paulus
1
@bernardpaulus,除了抛出ArithmeticException之外,它应该做什么?此外,该行为在API文档中得到了明确记录。 - Bombe
对于你的问题,没有一个完美的答案......我只是想指出,这种行为会导致错误,并不能通过使用 Math.abs(long) 来解决。很抱歉我的错误:我以为你提出了使用 Math.abs(long) 作为修复方法,而你只是简单地用它来“看到提问者期望的结果”。对不起。 - bernard paulus
在Java 15中,使用新方法实际上会抛出异常。 - chiperortiz

4

在Java 15中,针对这个问题有一个修复方法,它是将int和long转化成方法。它们将出现在类中。

java.lang.Math and java.lang.StrictMath

方法。
public static int absExact(int a)
public static long absExact(long a)

如果您通过了

Integer.MIN_VALUE

或者

Long.MIN_VALUE

抛出了一个异常。

https://bugs.openjdk.java.net/browse/JDK-8241805

我想知道如果传递Long.MIN_VALUE或Integer.MIN_VALUE,是否会返回一个正值而不是异常。


1

在Java中,2147483648不能存储在整数中,它的二进制表示与-2147483648相同。


0

但是 (int) 2147483648L == -2147483648。有一个负数没有正等价,因此它没有正值。您将在 Long.MAX_VALUE 中看到相同的行为。


-3

Math.abs对于大数并不总是有效,我使用的是我7岁时学到的一点代码逻辑!

if(Num < 0){
  Num = -(Num);
} 

这里的s是什么? - aioobe
抱歉,我忘记从我的原始代码中更新这一点。 - Dave
1
如果在代码片段之前Num等于Integer.MIN_VALUE,那么这会导致什么结果? - aioobe

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