在 JavaScript 开发中,我们经常会遇到这样的问题:
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false
这与我们在数学上的直觉不符。为什么会这样?又该如何解决呢?
本文将从底层原理、IEEE 754 标准出发,系统讲解浮点数精度丢失的原因,并提供实际开发中判断浮点数是否相等的解决方案。
一、根本原因:浮点数在计算机中的表示方式
JavaScript 中所有数字(包括整数和小数)都使用 64 位双精度浮点数(Double-precision floating-point) 表示,遵循 IEEE 754 标准。
✅ IEEE 754 双精度格式结构如下:
| 部分 | 占用位数 | 描述 |
|---|---|---|
| 符号位(Sign) | 1 位 | 表示正负数 |
| 指数位(Exponent) | 11 位 | 表示指数部分 |
| 小数位(Fraction / Mantissa) | 52 位 | 表示有效数字 |
由于二进制无法精确表示某些十进制小数(如 0.1 和 0.2),只能以近似值存储,这就导致了精度丢失。
二、具体分析:0.1 和 0.2 的二进制表示
🔹 0.1 的二进制:
0.00011001100110011...(无限循环)
🔹 0.2 的二进制:
0.0011001100110011...(无限循环)
这两个数在转换为二进制后都是无限循环小数,而 IEEE 754 规定最多只保留 52 位的小数部分,因此必须进行舍入处理。
最终,在计算机内部保存的实际上是这两个数的近似值,所以加起来的结果也不是精确的 0.3。
三、结果验证:0.1 + 0.2 等于多少?
console.log(0.1 + 0.2); // 0.30000000000000004
这个结果就是两个近似值相加后的误差累积结果。
四、解决方案:如何让浮点数“相等”?
✅ 方法一:使用 toFixed() 四舍五入比较
parseFloat((0.1 + 0.2).toFixed(1)) === 0.3; // true
📌 说明:
toFixed(n)返回一个字符串,保留 n 位小数;- 再使用
parseFloat()转回数字; - 注意:这种方式会丢失精度控制,不推荐用于金融计算;
✅ 方法二:使用 Number.EPSILON 判断误差范围(推荐)
ES6 引入了 Number.EPSILON,它是 JavaScript 中最小的可表示差值(约为 2^-52),可以用来判断两个浮点数是否足够接近。
function isEqual(a, b) {
return Math.abs(a - b) < Number.EPSILON;
}
console.log(isEqual(0.1 + 0.2, 0.3)); // true
📌 优点:
- 更科学、更严谨;
- 不依赖四舍五入;
- 适用于高精度场景;
五、一句话总结
JavaScript 使用 IEEE 754 双精度浮点数来表示数字,不能准确表示像
0.1和0.2这样的十进制小数,从而导致0.1 + 0.2 !== 0.3。要让它们“相等”,应使用Number.EPSILON判断误差范围或通过toFixed()控制精度。
💡 进阶建议
- 在金融计算、货币运算中使用
decimal.js或big.js等库避免精度问题; - 学习 IEEE 754 浮点数规范,深入理解浮点数的存储机制;
- 在 Vue / React 等框架中对数据做格式化展示时,结合
toFixed()和Number.EPSILON提升用户体验; - 使用 TypeScript 的类型系统提前发现潜在的数值错误;