浮点数精度问题
1. 核心原因
JavaScript 中的数字类型(Number)遵循 IEEE 754 标准,使用 双精度 64 位浮点数 存储。
计算机内部使用 二进制 表示数字:
- 0.1 (十进制) -> 0.0001100110011001100... (二进制)
- 0.2 (十进制) -> 0.0011001100110011001100... (二进制)
这两个数字在二进制中都是 无限循环小数。由于存储位数是有限的(52 位尾数),必须进行截断,导致精度丢失。
相加后再转换回十进制,结果是一个极为接近 0.3 但不等于 0.3 的数:
0.30000000000000004
2. 如何解决?
(1) 使用 Number.EPSILON (比较)
Number.EPSILON 是 ES6 引入的一个极小常量,表示 1 与大于 1 的最小浮点数之间的差值。可以用来判断两个浮点数是否足够接近。
function areEqual(n1, n2) {
return Math.abs(n1 - n2) < Number.EPSILON;
}
console.log(areEqual(0.1 + 0.2, 0.3)); // true
(2) 转换为整数计算
先将小数放大为整数(乘以 10^n),运算后再缩小回去。
function safeAdd(n1, n2) {
const m = Math.pow(10, 10); // 放大倍数
return Math.round((n1 * m) + (n2 * m)) / m;
}
console.log(safeAdd(0.1, 0.2)); // 0.3
(3) 使用第三方库
如果涉及金额计算,强烈建议不要手动计算,而是使用经过严格测试的库:
import Decimal from 'decimal.js';
new Decimal(0.1).plus(0.2).toString(); // '0.3'
(4) 使用 toFixed
如果只是为了页面展示,可以直接使用 toFixed 保留小数位。注意它返回的是字符串。
(0.1 + 0.2).toFixed(1); // "0.3"
3. 大数危机 (BigInt)
除了小数精度,还有一个大数精度问题。也就是超过 Number.MAX_SAFE_INTEGER (2^53 - 1) 的整数会丢失精度。
解决方案:使用 BigInt (ES2020)。
const bigNum = 9007199254740991n;
const newNum = bigNum + 2n; // 9007199254740993n (精确)
4. 面试总结
- 根本原因:IEEE 754 双精度浮点数存储导致的无限循环小数截断误差。
- 解决方案:如果是比较,用
EPSILON;如果是计算,转整数处理或用库;如果是展示,用 toFixed。