因为 JavaScript 采用 IEEE 754 双精度版本(64位),并且只要采用 IEEE 754 的语言都有这个问题。
我们知道,科学计数法中 30000 可以写成 3x104,以 10 为底数 4 为指数的科学计数法。在 IEEE754 标准中它是二进制数,底数为 2。
举个例子,十进制数 150,使用双精度浮点数表示,通过短除法计算,最后一个余数为高位值,于是拿到 150 对应的二进制数位 1001011,也就等于 2^8 * 0.1001011。表示如下:
1 | // D 表示十进制,B 表示二进制 |
小数的表示法采用的是乘二取整,与整数不同的是,第一个计算得到的整数位为最高位,故 0.1 对应的二进制数为 0.000110011(0011),也就等于 2^-4 * 0.1100110011(0011)。如 0.1,它的二进制表示为:
1 | // (0011) 表示循环 |
如果一个数既包含整数部分,又包含小数部分,其表示法的计算,需要分拆为整数和小数两部分,然后相加得到结果。
根据上面算出这些值:1
2
30.1D = 2^-4 * 1.1001100110011001100110011001100110011001100110011010B
0.2D = 2^-3 * 1.1001100110011001100110011001100110011001100110011010B
0.3D = 2^-2 * 1.0011001100110011001100110011001100110011001100110011B
0.1 + 0.2 时,先将两者指数统一为 -3,故 0.1 小数点向左移一位,于是:1
2
3
4 0.1100110011001100110011001100110011001100110011001101B
+ 1.1001100110011001100110011001100110011001100110011010B
------------------------------------------------------------
= 10.0110011001100110011001100110011001100110011001100111B
得到的二进制数为:
1 | 10.0110011001100110011001100110011001100110011001100111B |
小数点往左移一位使得整数部分为 1,此时尾数部分为 53 位,进一舍零,于是得到最后的值是:
1 | 2^-2 * 1.0011001100110011001100110011001100110011001100110100 |
这个值转化成真值为:0.30000000000000004。IEEE 754 双精度,六十四位中符号位占一位,整数位占十一位,其余五十二位都为小数位。因为 0.1 和 0.2 都是无限循环的二进制了,所以在小数位末尾处需要判断是否进位(就和十进制的四舍五入一样)。
那么 0.1 + 0.2 = 0.30000000000000004。
解决办法: parseFloat((0.1 + 0.2).toFixed(10)) 或者 Number.EPSILON