这是 JavaScript 面试中非常经典的问题,本质上不是 0.1 + 0.2 算错了,而是 IEEE 754 浮点数无法精确表示某些十进制小数。
1. 为什么 0.1 + 0.2 !== 0.3?
JavaScript 的 Number 使用 IEEE 754 双精度浮点数(64 bit) 。
十进制的 0.1、0.2 在二进制中都是无限循环小数:
scss
0.1 (十进制)
↓
0.000110011001100110011... (二进制)
所以 JavaScript 只能存储它们的近似值。
实际上可以近似理解为:
0.1 ≈ 0.10000000000000000555
0.2 ≈ 0.20000000000000001110
两者相加
≈ 0.30000000000000004441
因此:
arduino
0.1 + 0.2 === 0.3
// false
console.log(0.1 + 0.2)
// 0.30000000000000004
而 0.3 本身也只是一个接近 0.3 的浮点数,只是它和 0.1 + 0.2 最终得到的浮点表示不同。
2. 最常用的解决方案:误差范围比较
如果是普通数值计算,推荐使用误差范围:
javascript
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON
// true
更实用一点,可以封装:
javascript
function nearlyEqual(a, b, epsilon = Number.EPSILON) {
return Math.abs(a - b) < epsilon;
}
nearlyEqual(0.1 + 0.2, 0.3);
// true
不过要注意:对于特别大或特别小的数字 ,固定 Number.EPSILON 不一定合适,工程中通常会使用相对误差。
3. 金融金额:不要直接用浮点数
例如:
ini
const price = 0.1;
const quantity = 3;
console.log(price * quantity);
如果涉及金额、订单、支付、财务计算,更推荐把金额转换成整数的最小单位。
例如人民币使用"分":
ini
const price = 10; // 0.10 元 = 10 分
const quantity = 3;
const total = price * quantity;
console.log(total / 100);
// 0.3
也就是说:
0.1 元 → 10 分
0.2 元 → 20 分
0.3 元 → 30 分
整数计算就不会产生这种浮点误差。
4. 需要精确小数计算:使用 Decimal 库
例如使用 decimal.js:
sql
import Decimal from 'decimal.js';
const result = new Decimal('0.1')
.plus(new Decimal('0.2'));
console.log(result.toString());
// "0.3"
这里特别注意:
scss
new Decimal(0.1)
不如:
sql
new Decimal('0.1')
因为如果你先把 0.1 作为 JavaScript Number 创建出来,浮点误差已经产生了。
5. 现代 JavaScript 还可以考虑 BigInt
BigInt 可以精确处理整数:
ini
const a = 10000000000000000n;
const b = 20000000000000000n;
console.log(a + b);
// 30000000000000000n
但它不能直接处理小数:
arduino
0.1n // SyntaxError
所以金额场景依然通常是:
0.1 元 → 10n 分
而不是直接存储小数。
面试可以这样回答
JavaScript 的 Number 使用 IEEE 754 双精度浮点数,0.1 和 0.2 无法在二进制浮点格式中被精确表示,只能存储近似值,所以两者相加得到的结果与 0.3 的浮点表示并不完全相同。
普通计算可以使用误差范围比较,例如
Math.abs(a - b) < Number.EPSILON;涉及金额等精确小数计算时,应该使用整数最小单位,或者使用 Decimal 等高精度计算库。
一句话记忆:
markdown
0.1 + 0.2 !== 0.3
↓
IEEE 754 浮点数精度问题
↓
普通计算 → 误差比较
金额计算 → 整数分/厘
高精度小数 → Decimal