为什么0.1+0.2 ! == 0.3,如何让其相等

145 阅读3分钟

1. 问题描述

在我们的印象中,0.1+0.2 === 0.3是成立的,但是在JavaScript中,这个表达式的值却是false

在浏览器中,输入0.1+0.2,返回值是0.30000000000000004

image.png

2. JavaScript对数字的存储方式

JavaScript 中只有一种数字类型:Number,它的实现遵循IEEE 754标准,使用64位固定长度来存储,也就是标准的double双精度浮点数。

在二进制科学表示法中,双精度浮点数的小数部分最多只能保留52位,再加上前面的1,其实就是保留 53 位有效数字,剩余的需要舍去,遵从“0舍1入”的原则。

image.png

2-1. 将0.1转为小数

0.1 的二进制表示为: 0.00011001100110011001100110011001100110011001100110011001 10011...

科学技术法表示为: 1.1001100110011001100110011001100110011001100110011001*2^-4

可以看出0.1转为二进制后,符号位为0,指数为-4,小数为1001100110011001100110011001100110011001100110011001

2-2. 指数位保存方式

IEEE标准规定了一个偏移量,对于指数部分,每次都加这个偏移量进行保存,这样即使指数是负数,那么加上这个偏移量也就是正数了。由于JavaScript的数字是双精度数,这里就以双精度数为例,它的指数部分为11位,能表示的范围就是0~2047,IEEE固定双精度数的偏移量为1023

  • 指数位不全是0也不全是1时(规格化的数值),IEEE规定,阶码计算公式为 e-Bias。 此时e最小值是1,则1-1023= -1022,e最大值是2046,则2046-1023=1023,可以看到,这种情况下取值范围是-1022~1013即:指数加上1023,然后转为二进制,就是保存在指数位的值
  • 当指数位全部是0的时候(非规格化的数值),IEEE规定,阶码的计算公式为1-Bias,即1-1023= -1022。
  • 当指数位全部是1的时候(特殊值),IEEE规定这个浮点数可用来表示3个特殊值,分别是正无穷,负无穷,NaN。 具体的,小数位不为0的时候表示NaN;小数位为0时,当符号位s=0时表示正无穷,s=1时候表示负无穷。

对于上面的0.1的指数位为-4,-4+1023 = 1019 转化为二进制就是:1111111011

所以,0.1表示为:

0 1111111011 1001100110011001100110011001100110011001100110011001

对于这个问题,一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”。对JavaScript来说,这个值通常为2-52,在ES6中,提供了Number.EPSILON属性,而它的值就是2-52,只要判断0.1+0.2-0.3是否小于Number.EPSILON,如果小于,就可以判断为0.1+0.2 ===0.3