本篇文章最早源于我在知乎的回答,传送门。
52429作为常量经常出现在openjava中openjdk8-b132的java.lang.Integer类的456行为例子:
// 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;
}
固定的形式是一个整型变量乘上52429,然后向右移19位。很自然想到2^19=524288,那么这个52429就不难理解了,应该就是除10的操作。除法是很慢的,而乘法和以为却相对较快,所以为了优化这一点点的速度,java采用了乘52429再移19位的方法。对于为什么选择52429,这里列出几个取值的精度:
103/1024≈0.1006 (2^10) 205/2048≈0.100098 (2^11) …… 26215/262144≈0.100002 (2^18) 52429/524288≈0.10000038 (2^19) 104858/1048576≈0.10000038 (2^20)
可见,到2^19次方左右,除10的精度已经非常高了。那么为什么不再选几位呢?原因很简单:首先就是,java中整型占32位,而52429的二进制是1100110011001101(16位),65535的二进制是111111111111111(16位),相乘结果为31位。通过阅读上述源码的这个方法可知,i是不会大于65535的(注释中也提到:assert(i <= 65536, i);),所以算上符号位,能达到精度最大而又不溢出的也就是2^19了。
Sun公司的程序员对java的性能优化是非常仔细且彻底的,这一点在阅读openjdk的时候相信肯定会体会到。对于本文中除法的优化,按照现今计算平台来看,说实在的,几乎是没有效果的。但是java还是做了优化。在阅读openjdk的时候,还会遇到很多类似的东西,这时候就应该多多思考:这些代码有什么用?能不能换成别的?这样,相信对java也会有更深的理解。