toFixed 结果因精确度产生 bug
刀刀
4/7/2025
0 字
0 分钟
前情提要
在项目中有一个奇怪的现象,代码如下:
(2.55).toFixed(1); // 2.5
(2.45).toFixed(1); // 2.5
可以看到,toFixed
返回的结果是错误的。这是因为计算机计算浮点数存在精度错误的情况。
如果想要深入了解,必须先了解为什么计算机会存在精度问题。
精度问题
计算机在以下三个方面都会有可能造成精度丢失。
存储
一个十进制的小数,计算机会将其转为二进制,然后再存储到内存中。
但是计算机字符是有长度限制的,当转为二进制后字符过长,计算机会做 舍入 处理。如下所示:
0.0011001100 010011 // 截取后的第一位是0,因此前面保留的最后一位填0
0.0011001101 100110 // 截取后的第一位是1,因此前面保留的最后一位进1
由此,在计算机中,下面这个等式是成立的。
(0.20000000000000000001).toString(2) === (0.19999999999999999999).toString(2);
由此可见,存储的时候可能会存在不精确的情况。
计算
计算机做计算使用的不是十进制,而是二进制。计算后得到的依旧是二进制,最终转为十进制的数显示在浏览器上。
下面看几个例子:
0.2 + 0.1 = 0.300000000000000004
0.2 + 0.3 = 0.5
这是可能会有疑问,为什么 0.2 和 0.3 相加它的结果又精确了呢?这是因为有一些不精确它们会抵消掉。如 0.1 转为二进制最后会做入操作,0.2 转为二进制最后会做入操作.两个入因此最后结果会比原来的更大。0.3 转为二进制最后会做舍操作,和 0.2 相抵消,因此结果正常。
举一反三可以联想一下 0.3-0.2 的结果和 0.2-0.3 的结果,他们和原来的相比是更大还是更小呢?
0.3 是舍操作,0.2 是入操作,0.3 - 0.2 是更小的被减数减去更大的减数,得出的差会比原来的更小;0.2 - 0.3 是更大的被减数减去更小的减数,得出的差会比原来的更大。检验一下:
0.3 - 0.2; // 0.0999999999999999998
0.2 - 0.3; // -0.9999999999999999998
显示
既然 0.2 存到计算机里会出现二进制精度转换问题,为什么 console.log
打印的时候能够正常打印出 0.2 呢?
因为这是计算机做了近似匹配处理,计算机发现要打印的变量的二进制很相似 0.2,因此他会近似显示 0.2。而 0.1 + 0.2 的结果近似值与 0.3 的二进制相差甚远,因此最后没显示 0.3。
toFixed 本质
回到最开始的问题,为什么 toFixed
出现问题,这是因为 toFixed
本质也是在做计算,计算后再显示结果。
把他们的值以指定的精度返回该数值对象的字符串,结果如下:
因此最开始的问题也得到了解答。
解决方法
可以下载 Decimal
第三方库解决,其原理是在存储的时候不再存储数字,而是存字符串。在运算的时候循环字符串来做运算。
感兴趣可以去 github 阅读其源码 MikeMcl/decimal.js:JavaScript 的任意精度 Decimal 类型 (github.com) 。