Number精度问题
概要
在开发过程中,我遇到一个问题:接口返回的 Number 类型数据(约 16 位)与数据库中存储的值不一致,并且在多次请求时数值会出现莫名的偏大或偏小情况。最初我的解决方案是让后端将该字段由 Number 转换为 String 返回,以避免精度丢失。但当时并没有深入理解为什么数值会发生变化。
技术细节
先来看一道题:以下代码会输出多少次 loop?
javascript
let START = 2 ** 53
let END = START + 100
for (let i = START; i < END; i++) {
console.log('loop')
}答案是:不可预测。
原因在于 JavaScript 的 Number 类型采用 双精度浮点数(64 位 IEEE 754 标准) 表示。当整数超出安全范围后,无法保证精确表示,导致循环中的 i++ 不再是严格的逐一递增,可能会跳过某些数,甚至造成死循环或提前结束。
什么是安全整数范围?
JavaScript 能准确表示的最大安全整数是:
javascript
2 ** 53 - 1 === 9007199254740991
Number.MAX_SAFE_INTEGER === 9007199254740991超过这个范围,整数就会因为精度丢失而变得不可靠。例如:
javascript
Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2 // true这就是为什么大整数(例如 16 位订单号、雪花算法 ID 等)在前端处理时会失真。
解决方案
- 后端返回字符串:将大整数转换为字符串返回,例如 "9223372036854775807",前端按字符串处理,避免精度丢失。
- 使用大整数类型(推荐):ES2020 引入了 BigInt,可以安全表示任意精度的整数:
javascript
const big = 9223372036854775807n
console.log(big + 1n) // 9223372036854775808n- 序列化与存储时注意:如果涉及 JSON 传输,通常会转成字符串;在需要数值运算的场景下,可在前端用 BigInt 或专门的库(如 decimal.js、big.js)进行处理。
小结
一次偶然的接口请求让我发现:当返回的 Number 超过安全整数范围时,JavaScript 会出现精度丢失,表现为数值异常变大或变小。因此,遇到大整数相关的场景时,应当:用字符串存储和传输、或者使用 BigInt / 大数运算库,以此避免 JavaScript 在处理大整数时的精度损失问题。