在 JavaScript 中,浮点数运算可能会产生精度丢失的问题,尤其在处理 金额计算 时,这可能会导致严重的业务逻辑错误。例如:
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.07 * 100); // 7.000000000000001
console.log(0.1 + 0.7 === 0.8); // false
这些问题主要是由于 JavaScript 使用 IEEE 754 双精度浮点数(64 位)来表示数字,某些小数无法用二进制精确表示,从而导致精度丢失。
本篇文章将深入剖析 JavaScript 金额计算精度丢失的原因,并提供多种 解决方案 来避免这些问题。
JavaScript 所有的 Number 类型 都遵循 IEEE 754 双精度浮点数(64 位)标准,它的存储方式如下:
符号位 | 指数位 | 尾数位(小数部分) |
---|---|---|
1 位 | 11 位 | 52 位 |
但 某些十进制小数无法用二进制精确表示,类似于 1/3
在十进制中是无限循环小数 0.3333...
,而 0.1
在 二进制 下也会变成 无限循环小数:
0.1(十进制) = 0.000110011001100110011...(二进制 无限循环)
由于存储空间限制,JavaScript 只能截取前 52 位,导致数值被四舍五入,最终引发计算误差。
console.log(0.1 + 0.2); // 0.30000000000000004
解析:
0.1
的二进制近似值:0.00011001100110011...
0.2
的二进制近似值:0.0011001100110011...
0.30000000000000004
console.log(0.07 * 100); // 7.000000000000001
0.07
转换成二进制后是 0.000100011110101110000101...
,乘以 100
后的结果并不完全等于 7
。
核心思想:
避免小数计算,将小数转换为整数计算,计算完成后再转回小数。
function add(a, b) {
return (a * 100 + b * 100) / 100;
}
console.log(add(0.1, 0.2)); // 0.3
console.log(add(0.07, 0.02)); // 0.09
适用场景:
toFixed()
(简单但不推荐)console.log((0.1 + 0.2).toFixed(2)); // "0.30"
console.log((0.07 * 100).toFixed(2)); // "7.00"
缺点:
toFixed()
返回的是 字符串,如果要继续计算,还需转换回 Number
类型:let result = Number((0.1 + 0.2).toFixed(2)); // 0.3
Number.EPSILON
进行误差修正原理:
Number.EPSILON
是 JavaScript 最小的精度差值(约 2.22e-16
),可用于修正浮点计算误差:
function add(a, b) {
return Math.round((a + b + Number.EPSILON) * 100) / 100;
}
console.log(add(0.1, 0.2)); // 0.3
console.log(add(0.07, 0.02)); // 0.09
适用场景:
toFixed()
。JavaScript 原生 Number
无法解决所有精度问题,因此可以使用 BigDecimal 第三方库 来进行精准计算,例如 decimal.js。
安装:
npm install decimal.js
使用:
const Decimal = require('decimal.js');
console.log(new Decimal(0.1).plus(0.2).toNumber()); // 0.3
console.log(new Decimal(0.07).times(100).toNumber()); // 7
优点:
BigInt
(适用于整数金额计算)BigInt 可用于 大数计算,但 不支持小数:
const a = BigInt(100); // 1.00 元
const b = BigInt(200); // 2.00 元
console.log((a + b) / BigInt(100)); // 3n (表示 3 元)
适用场景:
方案 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
整数运算(推荐) | 金额计算 | 高效、适用范围广 | 需要手动转换 |
toFixed() |
仅限展示 | 简单易用 | 结果为字符串,无法继续计算 |
Number.EPSILON |
浮点修正 | 适用于加减运算 | 不适用于复杂运算 |
decimal.js(最佳) | 任何精度计算 | 高精度,API 强大 | 需引入库 |
BigInt |
整数计算 | 适用于大数运算 | 不支持小数 |
JavaScript 的浮点运算容易导致金额计算误差,我们可以通过 整数运算、Number.EPSILON
、BigDecimal 库等方式 来解决。
如果你正在开发电商、金融、结算系统,推荐使用 decimal.js
或整数运算,以保证计算精度。
你还遇到过 JavaScript 金额计算的问题吗?欢迎留言讨论!