跳转到内容

toFixed 结果因精确度产生 bug

刀刀

4/7/2025

0 字

0 分钟

前情提要

在项目中有一个奇怪的现象,代码如下:

js
(2.55).toFixed(1); // 2.5
(2.45).toFixed(1); // 2.5

可以看到,toFixed 返回的结果是错误的。这是因为计算机计算浮点数存在精度错误的情况。

如果想要深入了解,必须先了解为什么计算机会存在精度问题。

精度问题

计算机在以下三个方面都会有可能造成精度丢失。

存储

一个十进制的小数,计算机会将其转为二进制,然后再存储到内存中。

但是计算机字符是有长度限制的,当转为二进制后字符过长,计算机会做 舍入 处理。如下所示:

js
0.0011001100 010011 // 截取后的第一位是0,因此前面保留的最后一位填0
0.0011001101 100110 // 截取后的第一位是1,因此前面保留的最后一位进1

由此,在计算机中,下面这个等式是成立的。

js
(0.20000000000000000001).toString(2) === (0.19999999999999999999).toString(2);

由此可见,存储的时候可能会存在不精确的情况。

计算

计算机做计算使用的不是十进制,而是二进制。计算后得到的依旧是二进制,最终转为十进制的数显示在浏览器上。

下面看几个例子:

js
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 是更大的被减数减去更小的减数,得出的差会比原来的更大。检验一下:

js
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)