今天在重写Twitter 的分布式ID生成算法 sonw flake算法时,遇到一个问题,即一个整数在多次相乘后,输出的结果是0!对应的测试程序如下:
1
2
3
4
5
6int a = 1;
System.out.println(a);
for (int i =2;i<42;++i) {
a = a*2;
}
System.out.println(a);
后来思考了一下才反应过来,这个相乘的结果,远超过了Java int类型可以表示的范围。Java int类型可以表示的范围才是-$2^{31}$到$2^{31}$-1。
但是为什么输出结果是0呢?
因为计算机在存储数据时,最高位表示的是符号位,0表示正数,1表示负数,并且计算机中的数据使用补码来表示的。正数的补码是其本身,而负数的补码则是其反码加1(不包括符号位).
因为在执行“a = a*2”,当a=$2^{31}$时,在计算机中的表示已经变成变成了“10000000 00000000 00000000 00000000”,然后计算机在读取这个值的时候,就会把它当成负数来读,然后对应的值就是最小负数:-2147483648。
接下来,当执行到a=$2^{32}$,理论上值已经变成“1 00000000 00000000 00000000 00000000”,即33位,但是计算机在读取int类型时,只会读取32位,因此会舍弃掉最前面的“1”,因此读出来的结果就是0.
下面以short类型举例,说明一下数字在计算机中二进制的存储方式:
最小的负整数 -32768 计算机中二进制标示:10000000 00000000
最大的负整数-1 计算机中二进制标示:11111111 11111111
0 计算机中二进制标示:00000000 0000000
最小的正整数 1 计算机中二进制标示: 00000000 00000001
最大的正整数:32767 计算机中二进制标示: 01111111 11111111
最小负整数-32768加1之后,在计算机中的表示就是:10000000 00000001,对应的值就是- $ (2^{15} $ -1),即-32767。然后一直加1,直到11111111 11111111,对应的值就是-1,再加1,就变成了1 00000000 00000000,即17位,而short类型只读取16位,所以-1+1=0。00000000 00000000一直加1,加到01111111 11111111,就变成了short类型的最大整数32767。再加1,就变成了10000000 00000000,即最小负数:-32768。对应的循环图如下:
理解了这些之后就不难明白,为什么上一段程序的运行结果是0了。平时还是要多温故基础知识,否则很可能在一些不起眼的地方栽了坑。
参考资料