起因:
今天碰到这样一道题目,求a的b次方。思路很简单,就是用快速幂。
但是测试用例中有这样一个:a=2, b=-2147483648.
代码中我直接用了Math.abs()取了绝对值,这个用例就过不去。看了源码:
/**
* Returns the absolute value of an {@code int} value.
* If the argument is not negative, the argument is returned.
* If the argument is negative, the negation of the argument is returned.
*
* <p>Note that if the argument is equal to the value of
* {@link Integer#MIN_VALUE}, the most negative representable
* {@code int} value, the result is that same value, which is
* negative.
*
* @param a the argument whose absolute value is to be determined
* @return the absolute value of the argument.
*/
public static int abs(int a) {
return (a < 0) ? -a : a;
}
abs对Integer.MIN_VALUE返回的还是它自己,并且使用的就是"-"来取相反数。也就是说对于-2147483648取反还是-2147483648。所以我的代码错了。当然,特殊处理这个值代码就能过,可是我想知道为什么。
经过一番考虑,得出结论是这样的:
32位太长了,此处以8位为例。
先看一下,为什么8位整数表示 -128~127,主要考虑这个-128哪里来的
原码:
0 000 0000 +0
...
0 111 1111 +127
1 000 0000 -0
1 000 0001 -1
...
1 111 1111 -127
反码:
0 000 0000 +0
...
0 111 1111 +127
1 111 1111 -0
1 111 1110 -1
...
1 000 0000 -127
补码:
0 000 0000 +0
...
0 111 1111 +127
10 000 0000 -0 //-0求补码之后 舍弃超出的位和+0是一样的
1 111 1111 -1
...
1 000 0001 -127
1 000 0000 -128 //因为-0重叠了 少了一个数1 000 0000刚好就拿来作为-128
两个问题:
补码加减:
我们知道计算机之所以转换成补码,是因为补码可以连带着符号位直接二进制加减,非常方便。
来看-128 + 1和-128 - 1的效果:
-128 + 1:
1 000 0000
+ 0 000 0001
= 1 000 0001 = -127
注意这里都是补码运算,因为是计算机算的嘛
-128 - 1:
1 000 0000
- 0 000 0001
= 0 111 1111 = 127
1 000 0000
+ 1 111 1111
=10 111 1111 = 127 舍弃超出的位
可以看到 -128-1超出了8位能表示的范围下一位就是最大值127
不管直接-1还是+(-1)都是一样的结果
同样的道理 127 + 1 = -128。 这也就是和时钟的圈圈一个道理,形成了一个环。所以写算法的时候,需要考虑到这种边界情况。
用“-”取相反数:
127取相反数-127:
0 111 1111 对它进行-操作相当于下面两步
0 000 0001 求补
1 000 0001 符号位取反
结果等于 -127
-127取相反数-(-127):
1 000 0001 对它进行-操作相当于下面两步
1 111 1111 求补
0 111 1111 符号位取反
结果等于 127
所以结论就是:
-操作 等价于 求补然后符号位取反
其实逻辑也是这样 补码再求补就是原码 然后符号位取反就等于相反数 没毛病
-128取相反数-(-128):
1 000 0000
10 000 0000 求补 舍弃超出的位
1 000 0000 符号位取反
结果等于 -128
上面的解释说的很清楚了。
结论:
Integer.MAX_VALUE + 1 = Integer.MIN_VALLUE
Integer.MIN_VALLUE - 1 = Integer.MAX_VALUE
-Integer.MIN_VALLUE = Integer.MIN_VALLUE
abs(Integer.MIN_VALLUE) = Integer.MIN_VALLUE
在写算法的时候,如果题目就是明确要求涉及到了这些边界的加减求相反数这些操作,就要特殊考虑了。