问题描述
在 JavaScript 中整数和浮点数都属于 Number
类型,而在浮点数做运算的时候经常会出现一些精度问题,以下例子:
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.2 + 0.4); // 0.6000000000000001
console.log(0.1 + 0.7); // 0.7999999999999999
console.log(2.22 + 0.1); // 2.3200000000000003
console.log(0.3 - 0.2); // 0.09999999999999998
console.log(1.5 - 1.2); // 0.30000000000000004
console.log(2.3 - 2); // 0.2999999999999998
console.log(0.3 * 3); // 0.8999999999999999
console.log(0.57 * 7); // 3.9899999999999998
console.log(19.9 * 100); // 1989.9999999999998
console.log(35.7 * 100); // 3570.0000000000005
console.log(0.3 / 0.1); // 2.9999999999999996
console.log(0.7 / 10); // 0.06999999999999999
问题原因
不仅仅是 JavaScript,所有支持二进制浮点数运算的系统都存在这个问题。
其原因是,JavaScript 的 Number
类型是一个双精度 64 位二进制格式 IEEE 754 值,类似于 Java 或者 C# 中的 double
。IEEE 754 双精度浮点数使用 64 位来表示 3 个部分:
- 第 0 位:符号位,0 表示正数,1 表示负数
- 第 1 位到第 11 位:表示指数(-1022 到 1023)
- 第 12 位到第 63 位:表示尾数(有效数字)的数值部分(表示 0 和 1 之间的数值)
尾数(也称为有效数)是表示实际值(有效数字)的数值部分。指数是尾数应乘以的 2 的幂次。
尾数使用 52 比特存储,在二进制小数中解释为 1.…
之后的数字。因此,尾数的精度是 (可以通过 Number.EPSILON
获得),或者十进制数小数点后大约 15 到 17 位;超过这个精度的算术会受到舍入的影响。
一个数值可以容纳的最大值是 (指数为 1023,尾数为基于二进制的 0.1111…),可以通过 Number.MAX_VALUE
获得。超过这个值的数会被替换为特殊的数值常量 Infinity
。
只有在 到 范围内(闭区间)的整数才能在不丢失精度的情况下被表示(可通过 Number.MIN_SAFE_INTEGER
和 Number.MAX_SAFE_INTEGER
获得),因为尾数只能容纳 53 位(包括前导 1)。
更多详细信息,请参阅 ECMAScript 标准。
解决办法
1. 第三方库
大量的、精确的计算建议交给后端处理再返回。以下几个成熟的前端类库也可供使用:
Math.js
是 JavaScript 和 Node.js 的广泛数学库。它具有灵活的表达式解析器,并支持符号计算,并带有大量内置功能和常数,并提供了一种集成解决方案来处理不同的数据类型,像数字,大数字,复数,分数,单位和矩阵。
用于任意精确算术的 JavaScript 库。
为 JavaScript 提供十进制类型的任意精度数值。
2. toFixed()
使用 toFixed
缩小精度再使用 parseFloat
转成数字:
function strip(num, precision = 12) {
return +parseFloat(num.toFixed(precision));
}
Number
类型的 toFixed()
方法返回一个使用定点表示法来格式化该数值的字符串。
console.log(strip(0.1 + 0.2)); // 0.3
console.log(strip(0.2 + 0.4)); // 0.6
console.log(strip(0.1 + 0.7)); // 0.8
console.log(strip(2.22 + 0.1)); // 2.32
console.log(strip(0.3 - 0.2)); // 0.1
console.log(strip(1.5 - 1.2)); // 0.3
console.log(strip(2.3 - 2)); // 0.3
console.log(strip(0.3 * 3)); // 0.9
console.log(strip(0.57 * 7)); // 3.99
console.log(strip(19.9 * 100)); // 1990
console.log(strip(35.7 * 100)); // 3570
console.log(strip(0.3 / 0.1)); // 3
console.log(strip(0.7 / 10)); // 0.07