JavaScript 之 精度丢失问题

128 阅读1分钟

先看如下例子:

var p1 = 0.1 + 0.2
console.log(p1);    // 0.30000000000000004
console.log(p1.toString(2)); // 0.0100110011001100110011001100110011001100110011001101

var p2 = 1.5 - 1.4
console.log(p2);    // 0.10000000000000009
console.log(p2.toString(2)); // 0.000110011001100110011001100110011001100110011001101

var p3 = 0.2 * 3
console.log(p3);    // 0.6000000000000001
console.log(p3.toString(2)); // 0.100110011001100110011001100110011001100110011001101

var p4 = 0.3 / 0.1
console.log(p4);    // 2.9999999999999996
console.log(p4.toString(2)); // 10.111111111111111111111111111111111111111111111111111

计算结果和我们想的不一样,导致出现这种情况的原因,是因为在 JavaScript 中数字只有一种类型 number 采用双精度浮点数存储,遵循 IEEE 754 规范,使用64个比特位存储。

64比特位分为三个部分

  1. 1位: 符号位,表示整个数的正负值,0表示正,1表示负
  2. 11位: 指数位,用来表示次方数
  3. 52位:小数位,用来表示精确度, 超出的部分自动进一舍零,最后52位

上例代码中 0.1+0.2 的二进制为

0100110011001100110011001100110011001100110011001101...(1001无限循环)

然后小数位的长度为52,截取后转为十进制就成了 0.30000000000000004,造成了误差

经常出现

// 浮点数计算
console.log(0.1 + 0.2 == 0.3);
// toFixed 不进行四舍五入         // false
console.log(1.335.toFixed(2));
// 大整数计算                          // 1.33
console.log(9999999999999999 == 10000000000000001);         // true
// 长数值转换
console.log(parseFloat(9999999999999999.9));                // 10000000000000000
console.log(parseInt(9999999999999999));                    // 10000000000000000
console.log(parseFloat(9.999999999999999));                 // 9.999999999999998

解决方案

可以通过 mathjsdecimal.jsbignumber 等这种数学库,来操作计算。