Integer.toString()的两个疑问


源码

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位)时,它的值也是准确的

总结

  1. Integer.MIN_VALUE的绝对值会溢出,平时写代码需要注意
  2. q = (i * 52429) >>> (16+3),JDK nb!

文章作者: ❤纱雾
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 ❤纱雾 !
评论
  目录