JavaScript 小数精度问题
在 JavaScript 中,你可能遇到这样的现象:
console.log(0.1 + 0.2); console.log(2.3 * 10); console.log(2.3 * 100); console.log(2.3 * 10 === 23); console.log(2.3 * 100 === 230);
|
1. 浮点数的本质
JavaScript 的数字都是 64 位 IEEE 754 双精度浮点数。
1 bit | 符号位 11 bits | 指数 52 bits | 尾数 / 小数部分
|
十进制 0.1 = 二进制 0.0001100110011001100...(无限循环)
|
所以 2.3
只是一个近似值:
2.3 ≈ 2.29999999999999982236431605997495353221893310546875
|
2. 案例:2.3 _ 10 vs 2.3 _ 100
2.3 * 10
2.3 ≈ 2.2999999999999998 * 10 22.999999999999996 <-- 舍入成二进制 23
|
二进制示意:
真实值: 22.999999999999996 存储值: 23 显示值: 23 比较: 2.3*10 === 23 → true
|
2.3 * 100
2.3 ≈ 2.2999999999999998 * 100 229.99999999999997 <-- 无法舍入成二进制 230
|
二进制示意:
真实值: 229.99999999999997 存储值: 229.99999999999997 显示值: 229.99999999999997 比较: 2.3*100 === 230 → false
|
3. 打印规则
- JS 打印数字时选择 最短且能还原原数值的十进制表示。
- 能显示整数就显示整数,不能就显示近似小数。
22.999999999999996 → 23 229.99999999999997 → 229.99999999999997
|
查看真实存储值:
console.log((2.3 * 10).toPrecision(17)); // "23" console.log((2.3 * 100).toPrecision(17)); // "229.99999999999997"
|
4. 浮点数误差可视化
2.3 的近似值: 2.2999999999999998 乘 10 → 23 (可舍入) 乘 100 → 229.99999999999997 (无法舍入)
|
5. 解决方法
5.1 四舍五入
Math.round(2.3 * 100); // 230
|
5.2 保留小数
(0.1 + 0.2).toFixed(2); // "0.30"
|
5.3 使用整数运算
let price = 230; // 2.3元 -> 230分 let total = price * 10; console.log(total / 100); // 23
|
5.4 使用大数库
import { Decimal } from "decimal.js"; let x = new Decimal(2.3).times(100); console.log(x.toString()); // "230"
|
6. 总结
- JS 数字是采用 64 位 IEEE 754 双精度浮点数存储,小数可能无法精确表示,这其实并不是 js 的问题,而是双精度浮点数机制的问题,同样 java 也会存在小数精度丢失的问题。
- 打印整数或小数由 最短表示法规则 决定。
- 严格相等比较(
===
)取决于浮点数的 二进制存储。
2.3*10 === 23
是因为误差正好舍入,2.3*100 !== 230
是误差无法舍入。
- 遇到小数运算、金额计算时,最好用 四舍五入、整数运算或大数库。
查看真实存储值:
console.log((2.3*100).toPrecision(17)); // 229.99999999999997
|