这个Java绝对值函数怎么连负数都不绝对了?

摘要:Java 中的 Math.abs() 方法居然不绝对,这个坑你踩过么?快来看看这到底是个什么坑吧!!!
现象 假如有如下代码定义了一个方法 test(),它入参可以任何一个 int 类型的整数,那么它输出结果可能是什么? public class Test { public static void test(int a) { System.out.println("The result of absolute value compare to zero is:" + (Math.abs(a) >= 0)); } } 如果你的结论是 true,那么恭喜你,你掉入到绝对值不绝对的坑里面了。这个方法输出的结果有可能是 true,但是也有可能是 false。比如下面的调用代码将分别输出 true,true,true , false,如下图所示: public static void main(String[] args) { test(1); test(-1); test(Integer.MAX_VALUE); test(Integer.MIN_VALUE); } 在 Java 中,通过 Math.abs() 函数返回的值有的时候并不是这个数的绝对值。如下面的代码所示: public static void main(String[] args) { System.out.println(Math.abs(Integer.MIN_VALUE)); } 上面的代码输出的结果并不是 Integer.MIN_VALUE 的绝对值,输出的结果是它自己,如下图所示: 从输出可以看到因为 Math.abs(Integer.MIN_VALUE) 的结果还是 Integer.MIN_VALUE,因此它是小于 0 的,这个也解释了上面判断大于等于 0 结果有可能输出的是 false。 为什么 Math.abs(Integer.MIN_VALUE) 的结果还是 Integer.MIN_VALUE 而不是它的绝对值呢? 原理 从 int 类型可以表示的数的范围解释是比较好理解的。以 int 类型为例,它能够表示的范围是 2^31 到 2^31 - 1。即 -2147483648 到 2147483647,可以看到最小的负数是 -2147483648 。它的绝对值实际上应该是 2147483648,但是这个值已经超过了 int 类型能够表示的最大的数 2147483647 了。如果返回 2147483648,它是不能在一个 int 类型的数中表示的。如果我们直接把 2147483648 这个数赋值给一个 int 类型的变量,编译器也会提示 Integer number too large,如下图所示: 因此这里 Math.abs() 函数返回的结果并不能是 214748364,因为 int 类型根本表示不了这个数。 那 Math.abs() 方法做了什么操作呢?查看 Math.abs() 方法的源码,实现逻辑如下: 在方法中就是判断了一下这个数是否小于 0,如果小于 0 的话,就返回对这个数取反后的值。那这个取反操作具体做了什么事情呢?为什么对 Integer.MIN_VALUE 进行了求反操作返回的还是它自己? 要回答这些问题,那就得知道计算机底层是表示一个整数的方式以及 int 类型表示的数的范围是 2^31 到 2^31 - 1 的原因。 Java语言规范中对此做了描述,规范中说到 Java 语言中使用 「two's-complement representation」 来表示整数,因为 「two's-complement representation」 的值不是对称的,所以对 int 或者 long 类型的最小值的取反的结果还是它们自身,在这个场景是有「溢出」发生的。而对一个整数的取反操作相当于把它的所有比特位取反,然后再加上 1。如下图所示: 规范这里的提到的 「two's-complement」 就是我们常常说的「补码」,学过计算机组成原理相关课程的应该对这个词语比较耳熟。 补码就是将二进制位的最高位作为符号位,它的权重是 -2^(w -1) (这里的 w 为比特位的个数) ,如果它设置为 1 表示负数,如果设置为 0,表示非负数。如下图所示: 根据补码的定义来看,补码能够表示的最大的数是 2^(w -1) - 1,而它能够表示的最小的的数是 -2^(w -1) (这里的 w 为比特位的个数)。那么最小数的绝对值是比最大数的绝对值还要大 1 的。从上面的图也可以看出(上图中的比特位数为 4),数轴最左侧的刻度是 -8,而数轴最右侧的刻度是 7。 对于补码的取反操作是把每个比特位都取反,然后加上 1。
阅读全文