问题描述

在对文件上传进度进行格式化处理时,涉及到精度的四舍五入问题,例如保留两位小数。这在JS中可以通过调用toFixed传入精度实现。但问题来了,先乘100再舍入,还是先舍入再乘100?

先看结果

javascriptCopy code
  • 1
  • 2
  • 3
  • 4
  • 5
var a = Math.random(); console.log(a); console.log(a.toFixed(4)); console.log(a.toFixed(4) * 100); console.log((a * 100).toFixed(2));
var a = Math.random(); console.log(a); console.log(a.toFixed(4)); console.log(a.toFixed(4) * 100); console.log((a * 100).toFixed(2));

输出1:

javascriptCopy code
  • 1
  • 2
  • 3
  • 4
0.8988301526666329 0.8988 89.88000000000001 89.88
0.8988301526666329 0.8988 89.88000000000001 89.88

输出2:

javascriptCopy code
  • 1
  • 2
  • 3
  • 4
0.413465264585297 0.4135 41.349999999999994 41.35
0.413465264585297 0.4135 41.349999999999994 41.35

输出3:

javascriptCopy code
  • 1
  • 2
  • 3
  • 4
0.1348901139915517 0.1349 13.489999999999998 13.49
0.1348901139915517 0.1349 13.489999999999998 13.49

输出4:

javascriptCopy code
  • 1
  • 2
  • 3
  • 4
0.2731272622047103 0.2731 27.310000000000002 27.31
0.2731272622047103 0.2731 27.310000000000002 27.31

可以看出,两种调用顺序的执行结果并不一致,前者甚至没有达到预期的保留2位小数。该如何解释并解决呢?

回到JS对浮点数的处理,JS仅有Number数值类型,而Number采用的是IEEE-754 64位双精度浮点数编码。其具有以下特点:

  1. 浮点数可表示的值范围比同等位数的整数表示方式的值范围要大得多;
  2. 浮点数无法精确表示其值范围内的所有数值,而有符号和无符号整数则是精确表示其值范围内的每个数值;
  3. 浮点数只能精确表示m*2e的数值;
  4. 当biased-exponent为2e-1-1时,浮点数能精确表示该范围内的各整数值;
  5. 当biased-exponent不为2e-1-1时,浮点数不能精确表示该范围内的各整数值。

由此可知,部分数值并不能精确表示,最终在运算结果中导致了偏差。

对以上问题的分析,详见:Why 0.1 + 0.2 === 0.30000000000000004?。其中也给出了几个解决的方案,可以作为问题的补充。

而对于本问题的解决,将toFixed调用放至最后即可。

总结

  1. 调用顺序很重要!
  2. 异常测试很重要,尤其对于条件特殊值的非边界情况(如特定的随机数)。