CC BY 4.0(除特别声明或转载文章外)
上篇博客对Java中浮点数的精度进行了研究,但是只研究了float和double本身的情况,那么如果它们之间互相转换,精度又会如何呢?
一、初步实验
代码:
float a = 1.1f;
double b = 1.123123123123123123123123;
System.out.println((double)a);
System.out.println((float)b);
运行结果:
1.100000023841858
1.1231232
看来,float转double会产生偏差,而double转float则要小心溢出。
二、偏差哪来的?
为什么float中正常存储的1.1会变成1.100000023841858这个数字,而且重复几次运算后得到的结果相同,看来这个偏差并不是随机的,而是有规律可循的。为了找到这个规律,咱们将视角放到底层看看。
上篇博客中说过,浮点数的存储是依靠指数乘尾数的等比数列加和式,那么我们用同样的办法将1.1分解看看。
$1+2^{-4}+2^{-5}+2^{-8}+2^{-9}+2^{-12}+2^{-13}+2^{-16}+2^{-17}+2^{-20}+2^{-21}+2^{-23}=1.100000023841858$
$1+2^{-4}+2^{-5}+2^{-8}+2^{-9}+2^{-12}+2^{-13}+2^{-16}+2^{-17}+2^{-20}+2^{-21}=1.0999999046325684$
由于float只有23位尾数,所以这里最小只分解到2的-23次方。发现了吗?上面的两个数字中是不是有一个特别熟悉?
三、结论
没错,也就是说,float里面存储的1.1其实并不是1.1,其本质就是1.100000023841858,只不过因为float的精度问题,将后面的小数部分舍去了罢了。而这个问题,当将精度扩充为double后,就暴露了出来。
同理我们也可以类推出,double中存储的1.1也不是真的1.1,而是一个十分接近1.1的数字,却又因为double的精度限制而显示不出来、进一步加强了上篇博文浮点数近似、粗略、不连续的结论。
由此可以总结出:float转double时,如果该数字不能在23位尾数以内精确表示,那么一定会产生偏差。
什么叫在23位尾数以内精确表示呢?比如$1=1+0、1.5=1+2^{-1}、…$
对于这类数字,它可以从float精确转换为double而不产生偏差,因为float转double只不过通过补位将不足的位数以0代替,那么float就可以精确表示的数字,转double就是无损转换。