源码
public static String toString(int i) {
if (i == Integer.MIN_VALUE)
return "-2147483648";
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] buf = new char[size];
getChars(i, size, buf);
return new String(buf, true);
}
疑问一
为什么Integer.MIN_VALUE
需要单独进行判断?
光看这一句判断语句看不出什么,直接看这个方法的具体实现getChars
static void getChars(int i, int index, char[] buf) {
int q, r;
int charPos = index;
char sign = 0;
if (i < 0) {
sign = '-';
i = -i;
}
// Generate two digits per iteration
while (i >= 65536) {
q = i / 100;
// really: r = i - (q * 100);
r = i - ((q << 6) + (q << 5) + (q << 2));
i = q;
buf [--charPos] = DigitOnes[r];
buf [--charPos] = DigitTens[r];
}
// Fall thru to fast mode for smaller numbers
// assert(i <= 65536, i);
for (;;) {
q = (i * 52429) >>> (16+3);
r = i - ((q << 3) + (q << 1)); // r = i-(q*10) ...
buf [--charPos] = digits [r];
i = q;
if (i == 0) break;
}
if (sign != 0) {
buf [--charPos] = sign;
}
}
在第6行可以清楚的看到,负数进行了单独一次判断,如果是负数,那么会在buf
中加上负号,然后将数字i
变为其绝对值。
到这里就明白了。
根据计组中学过的补码知识可以得到,32位int类型的范围是-231 ~ 231 - 1,即-2147483648 ~ 2147483647,所以当i
最小时,绝对值等于2147483648溢出
所以Integer.MIN_VALUE
需要单独处理
疑问二
getChars
中对小于等于65536的数字进行的处理,如下代码中,
q = (i * 52429) >>> (16+3);
的意义
for (;;) {
q = (i * 52429) >>> (16+3);
r = i - ((q << 3) + (q << 1)); // r = i-(q*10) ...
buf [--charPos] = digits [r];
i = q;
if (i == 0) break;
}
根据整个方法可以猜到这句代码的作用是整除10,但为什么写成这样值得探究,当然不猜都知道是为了提高效率
2.1
首先,219=524288,距离52429的十倍只差了12,可以说是非常接近了,那么52429 / 524288 也非常接近0.1
算到这我还去百度了一下“计算机中整除是怎么实现的”
不过一篇文章都没,想了想也对,计算机只有加法和移位,整除应该是各个语言自己实现的
虽然不知道java中整除是怎么实现的,但是整除运算能针对任何整数,想必算法也是十分复杂的。
而getChars
中只需要整除10,那么 * 52429 / 524288 确实是最快的,只做了两次运算
2.2
好了 还剩下一个问题,精度问题。
在上面可以看到 52429 / 524288 的精度达到了小数点后6位,也就是说,任何6位数或更小的数,乘上 52429 / 524288 都可以看做乘上0.1
源码中对大数已经做了一次处理,到这里的时候一定是小于等于65536的,也就符合了这个精度要求。
但是随之而来一个新的问题
q = (i * 52429) >>> (16+3)
65536 * 52429 不会溢出吗?
答案是会
65536 是 2 的16次,而52429大于32728,即大于2的15次
所以 65536 * 52429 一定是大于 231,已经溢出了,实测输出 -858980352
JDK 牛逼的地方就来了
- 溢出为什么通常会变成负数?因为占用了第一位符号位,变成了1,而符号位为1就代表着负数。
- 52429小于65536,所以乘积也小于232,那么32位二进制数是可以容下这个数的。
- int 第一位是符号位。但是,本质上这32位就是一个二进制数,
- 那么有没有什么办法让java把这串补码看做普通的二进制数(即不考虑符号位)呢,有。
>>>
<<
和>>
想必都不陌生了,表示左移和右移。而<<<
>>>
表示无符号左移和无符号右移,右移时,不论正负,高位都是补0- 既然 65536 * 52429 的二进制真值是准确保存在 int 的32位中的,那么当它除以524288(
>>>
右移19位)时,它的值也是准确的
总结
Integer.MIN_VALUE
的绝对值会溢出,平时写代码需要注意q = (i * 52429) >>> (16+3)
,JDK nb!