昨天有同学反馈使用 toFixed
进行保留小数点的操作时遇到了诡异的问题。
在本机chrome模拟复现这个问题:
从结果来看,floor、ceil还是round,感觉浏览器的处理方式似乎毫无章法可循。
但是程序的运行总是要有逻辑的,我们先看下规范怎么规定的。
toFixed() returns a string representation of numObj that does not use exponential notation and has exactly digits digits after the decimal place. The number is rounded if necessary…
大概意思是,toFixed返回字符串,并且不使用指数形式,如果有必要会四舍五入...
单看定义还是没有头绪,也无法解释我们上面的现象。
ecma-262
里关于 toFixed
相关 规范 的描述:
其中第8条里的描述
简单翻译下就是:找到 n / 10^f - x
最接近 0 的数值,如果这样的值有两个,则使用较大的那个
举个例子,比如上面的 3.05,用这个规则就是:
n / 10 - 3.05 ~ 0
到这里,问题其实就转换为,到底是 3.1 离 3.05 近,还是 3.0 离 3.05 近,
如果让我们分辨,很容易得出3.1和3.0距离3.05一样近,也就是 3.1 和 3.0 一样,
然后根据上面规则,选择较大的那个,也就是 3.1
相比较,通过规范来计算的结果应该是 3.1,但是我们测试的是 3.0。
是浏览器自行一套体系吗?
这里需要明确一点,规范是规范,但是对于浏览器如何实现,得看浏览器的实现细节。
比较哪个数值离得近,我们可以尝试猜测浏览器可能会怎么做(当然也可以看v8源码
+-><
我们跑几个例子看下:
上面运行结果能看出,小数运算导致的偏差和 toFixed
的诡异结果是一致的。
|3.0-3.05|
< 0.5, |3.1-3.05|
> 0.5,则得出3.0和3.05“离”的更近,所以 3.05.toFixed
是 3.0
。
所以验证了之前的猜测,问题出在小数运算导致的在判断哪个目标值离当前数值最近的时候了。
到这里,其实就能有结论了,当出现 0.5
这种情况时,js引擎考虑是否进位的时候,根据规范是需要对比哪个数与当前值更近,然后在小数运算的时候出现了误差(IEEE 754)。
所以使用toFixed其实是不够精确的,可使用Math.round模拟。
参考文档: