如果你在浏览器控制台输入 0.1 + 0.2,得到的答案不是 0.3,而是 0.30000000000000004。这不是 JavaScript 的 bug,而是所有使用 IEEE 754 双精度浮点数标准的语言(如 Python、Java、C 等)都存在的固有问题。理解它并不需要高深的数学,只需要知道两件事:为什么会出现,以及怎么处理。
为什么 0.1 + 0.2 ≠ 0.3
计算机内部使用二进制存储数字。十进制中能精确表示的小数(如 0.5 = 1/2),在二进制中同样能精确表示。但 0.1 和 0.2 这类十进制小数,在二进制中是无限循环小数:
- 0.1(十进制) = 0.0001100110011001100110011...(二进制,1100 循环)
- 0.2(十进制) = 0.001100110011001100110011...(二进制,1100 循环)
JavaScript 的 Number 类型采用 64 位双精度浮点数(1 位符号 + 11 位指数 + 52 位尾数),存储空间有限,只能截断到 52 位。这个截断引入了一个极微小的“舍入误差”。当多个带有误差的数进行运算时,误差可能被放大,最终体现在十进制的显示结果上。
什么场景会踩坑
- 相等比较:
0.1 + 0.2 === 0.3返回false。 - 金额计算:如果把商品单价用浮点数存储并累加,最后的总额可能对不上账。
- 循环累加:
for (let i = 0; i < 10; i += 0.1)可能因为误差导致循环次数与预期不符。 - toFixed 舍入:
(1.335).toFixed(2)在某些引擎中返回"1.33"而非"1.34",因为内部表示可能略小于 1.335。
实用解决方案
1. 容忍一个极小误差来判断相等
不直接比较 a === b,而是看它们差的绝对值是否小于一个极小值(通常取 Number.EPSILON,即 2 的 -52 次方):
function isEqual(a, b) {
return Math.abs(a - b) < Number.EPSILON;
}
isEqual(0.1 + 0.2, 0.3); // true
适用于比较运算结果,但不能从根本上解决精度问题。
2. 使用 toFixed 或 toPrecision 格式化显示
当只需要向用户展示时,用 toFixed(n) 指定保留小数位数。注意 toFixed 返回的是字符串,且可能存在舍入问题,更适合最终展示,不适合中间运算。
const result = 0.1 + 0.2;
console.log(result.toFixed(1)); // "0.3"
3. 将小数转为整数进行运算(核心:金额场景的最可靠方案)
把小数乘以 10 的 n 次方变成整数,计算完再除回来。因为整数在 53 位范围内是精确的:
// 计算 0.1 + 0.2
const result = (0.1 * 10 + 0.2 * 10) / 10; // 0.3
实际开发中,可以封装一个通用的数学运算库,例如:
function add(num1, num2) {
const baseNum1 = (num1.toString().split('.')[1] || '').length;
const baseNum2 = (num2.toString().split('.')[1] || '').length;
const base = Math.pow(10, Math.max(baseNum1, baseNum2));
return (num1 * base + num2 * base) / base;
}
不过,这种手写方案在多个连续运算时比较繁琐,且需注意乘除本身也可能引入浮点误差。
4. 使用第三方库(推荐用于生产环境的金额/精密计算)
如果业务涉及金融、订单、计价等场景,直接用成熟的高精度计算库:
- decimal.js:提供任意精度的十进制运算,API 贴近原生 Number。
- big.js:体积更小,适合基础运算。
- bignumber.js:功能全面,支持多种进制和格式化。
import Decimal from 'decimal.js';
const result = new Decimal(0.1).plus(0.2);
console.log(result.toString()); // "0.3"
5. 在明确整数范围内,使用 BigInt
对于超出 Number 安全范围的大整数(如 16 位以上的 ID),可以使用 ES2020 引入的 BigInt:
const big = 9007199254740991n + 1n; // 精确的整数运算
但 BigInt 不支持小数,因此不能直接解决浮点精度问题。
选择建议
- 单纯展示:用
toFixed或Intl.NumberFormat格式化。 - 比较运算结果:用
Math.abs(a - b) < Number.EPSILON。 - 多次累加、加减金额:用整数法(乘以 100 后再运算,最后除以 100)或直接引入第三方库。
- 生产级的金融或科学计算:毫不犹豫使用 decimal.js 等专业库,不要自己造轮子。
理解浮点精度问题不是为了追求“绝对精确”(没有系统能做到无限精度),而是在合适的场景下选择正确的方法,让误差不在业务逻辑中累积成事故。