做开发时经常要处理接口返回的 JSON 数据,尤其是金额、坐标、科学计算这类对精度要求高的场景。可有时候你会发现,明明接口传过来的是 123456789012345.67,结果一解析变成 123456789012345.66 或干脆是 123456789012345.7,精度就这么丢了。
问题出在哪?
根源在浮点数表示。JavaScript 中所有数字都是双精度浮点数(IEEE 754),能安全表示的整数范围是 -2^53 + 1 到 2^53 - 1,也就是 -9007199254740991 到 9007199254740991。超过这个范围,小数部分或末几位就可能被舍入。
比如下面这个 JSON:
{
"orderId": 12345678901234567,
"amount": 999999999999999.99
}
直接用 JSON.parse() 解析后,orderId 和 amount 都会出问题,因为它们超出了安全整数范围。
方案一:把大数转成字符串
最简单的方法是在服务端就把长数字当字符串传。虽然前端拿到的是字符串,但至少不会丢精度。
{
"orderId": "12345678901234567",
"amount": "999999999999999.99"
}
前端再根据需要转成 BigInt 或使用专门的高精度库处理。
方案二:自定义 parse 函数,拦截数字字段
如果不能改接口,可以在解析时手动控制。利用 JSON.parse 的第二个参数——还原函数(reviver)。
const data = `{"id":12345678901234567,"price":999999999999999.99}`;
const result = JSON.parse(data, (key, value) => {
// 假设 id 和 price 需要高精度
if (key === 'id' || key === 'price') {
return String(value); // 转成字符串保留原样
}
return value;
});
console.log(result); // { id: '12345678901234567', price: '999999999999999.99' }
这样就能在解析阶段就把关键字段“保护”起来,避免被当成 JS 数字处理。
方案三:用 BigInt 处理超大整数
如果是纯整数,比如订单号、用户 ID,可以考虑用 BigInt。
const result = JSON.parse(data, (key, value) => {
if (key === 'id') {
return BigInt(value);
}
return value;
});
console.log(result.id); // 12345678901234567n
注意 BigInt 不能和普通数字混算,运算时要统一类型。
方案四:引入高精度计算库
涉及金额计算时,推荐用 decimal.js 或 big.js 这类库。
import Decimal from 'decimal.js';
const result = JSON.parse(data, (key, value) => {
if (key === 'price') {
return new Decimal(value);
}
return value;
});
// 后续计算就不会有浮点误差
const total = result.price.times(2);
这类库内部用字符串模拟十进制运算,完全避开浮点数坑。
实际项目中的建议
在公司做支付系统时,我们就吃过这亏。一开始订单金额用 number 传,测试时好好的,上线后发现几毛钱对不上,查了半天才发现是 JSON 解析时自动转 float 导致的舍入。
后来统一改成金额字段用字符串传输,前端收到后用 Decimal 处理,再也没出过问题。团队也加了代码规范:涉及金额、ID、地理坐标等,一律不信任原始 number 类型。
接口文档也明确标注哪些字段是“高精度数值”,后端输出时尽量配合做字符串化处理。