经典的面试题:为什么 0.1 + 0.2 ≠ 0.3?
编程界很经典的一个问题:为什么0.1 + 0.2 ≠ 0.3?面试中突然被问起,一时之间却不知道怎么回答。只记得与二进制有关,但具体是怎么来着,却有点说不清楚了。
后来查找了相关资料,才知道0.1 + 0.2 ≠ 0.3的来龙去脉。
简单来说:
0.1的二进制是:0.0001100110011...(无限循环0011)0.2的二进制是:0.001100110011...(无限循环0011)
由于计算机的存储空间有限,所以两个无限循环的二进制必须被截断存储,从而使两个数丢失了原本的精度。
0.1实际存储的值是0.10000000000000000555111512312578270211815834045410156250.2实际存储的值是0.200000000000000011102230246251565404236316680908203125
两数相加:
0.10000000000000000555...
+
0.20000000000000001110...
=
0.3000000000000000444...其结果被浮点精度截断后就是:0.30000000000000004。
到这里,基本上就可以讲清楚0.1 + 0.2 ≠ 0.3的问题了。
如果还要进一步深挖,你可能还有很多疑问:
- 小数的二进制是如何转换的?
- 计算机是如何存储小数的?
- 计算机是如何处理小数相加的?
小数的二进制转换
我们知道,整数的二进制表示:b * 2^(n - 1),b表示二进制位的系数,n表示第几位。比如十进制6可以表示:
6 = 1 * 2^2 + 1 * 2^1 + 0 * 2^0
= 110同理,小数部分的二进制表示方法类似:b * 2^(-n)。比如:0.25可以这样表示:
0.25 = 0 * 2^-1 + 1 * 2^-2
= 0.01对应的二进制就是:0.01。
计算小数部分的二进制方法可以使用乘2取整法:
举例:0.625的二进制:
0.625 * 2 = 1.25,整数1,小数0.25;0.25 * 2 = 0.5,整数0,小数0.5;0.5 * 2 = 1,整数1,小数0,结束。
顺序排列,就可以得到:0.101。
相同的方法就可以计算出0.1和0.2的二进制,有兴趣的话可以自行验证。
计算机如何存储小数
计算机通常都是使用IEEE 754 标准的双精度浮点数进行小数的存储,也是就64bit。
存储结构:
1bit 符号位
11bit 指数
52bit 尾数实际能存储的小数的二进制尾数只有52位,超出后就会被截断。
所以,一个小数在计算机中其实是通过指数表示法来存储的,比如:0.625,对应的二进制是0.101。指数表示就是:
-1^s * 1.01 * 2^-1s是符号位,0或1,上例中s = 0;01存储在尾数部分,1.不需要存储,默认隐含;-1需要加上1023后,以二进制形式存储在指数部分。
小数的加法计算
基于小数的存储结构,做加法运算时,必须先统一指数(也就是对阶),然后再进行二进制加法运算。
我们再来梳理一下0.1 + 0.2的计算过程:
1. 0.1的二进制
0.1 = 0.0001100110011001100110011001100...
# 实际有 52 位尾数,便于书写已省略,下同
≈ 1.1001100110011 × 2^-42. 0.2的二进制
0.2 = 0.0011001100110011001100110011...
≈ 1.1001100110011 × 2^-33. 对阶
0.1 ≈ 0.11001100110011 × 2^-3
0.2 ≈ 1.10011001100110 × 2^-34. 相加
1.10011001100110
+0.11001100110011
-----------------
10.01100110011001结果就是:
10.01100110011001 × 2^-35. 规格化
小数点左移 1 位
1.001100110011001 × 2^-2最后转换成十进制后就是0.30000000000000004(注意:上面的数据不是完整的52位尾数,若要验证需要使用完整的52位尾数进行计算)。
最后
总结几个关键字:转二进制→无限循环→截断存储→丢失精度。
好了,下次如果再被问到类似的题目,就可以扯一趴啦了。