你给某支付平台做「交易流水导出」功能。
需求很直接:把数据库里 bigint(20)
的订单 ID 渲染到表格里。
你顺手写了这么一行:
js
// ❌ 线上事故代码
const row = `<tr><td>${order.id}</td><td>${order.amount}</td></tr>`;
结果上线第二天,财务发现:
"有笔 1.8e+17 的订单,点进去详情金额对不上!"
排查发现:
- 数据库 ID 是
18012345678901234567
; - 但 JS 里
Number(order.id)
变成了18012345678901234000
------ 尾部 567 直接丢了; - 因为它超过了
Number.MAX_SAFE_INTEGER
(9007199254740991),JS 的 64 位浮点数精度崩了。
这可不是显示问题,而是 ID 错位导致查串了订单,差点引发资损。
解决方案:三层防御,把大数关进"安全笼"
1. 表面用法:用 BigInt
代替 Number
js
// ✅ 正确处理大数
const bigId = BigInt("18012345678901234567"); // 🔍 字符串转 BigInt
console.log(bigId.toString()); // "18012345678901234567"
// 用于计算
const nextId = bigId + 1n; // 🔍 必须加后缀 n
关键点:
- 必须用字符串初始化 ,
BigInt(18012345678901234567)
会先被转成Number
再转BigInt
,已经丢精度了; - 运算时操作数必须都是
BigInt
,不能和Number
混算; - 比较可以用
===
,但==
会自动转换,有坑。
2. 底层机制:为什么 JS 数字会"失精"?
类型 | 存储方式 | 范围 | 精度 |
---|---|---|---|
Number |
IEEE 754 双精度浮点 | ±1.79e+308 | 53 位有效数字 |
BigInt |
任意长度整数 | 无上限 | 完全精确 |
原理图(文字版):
flowchart LR
A["Number: [1位符号][11位指数][52位尾数]"] --> B["实际精度 2^53 - 1 = 9007199254740991"]
B --> C["超过这个值,尾数不够用,低位被舍入"]
所以 9007199254740992 === 9007199254740993
在 JS 里居然是 true
!
3. 设计哲学:从"传输"到"渲染"全链路防溢出
(1)接口层:后端传字符串,前端不碰大数
json
{
"order_id": "18012345678901234567", // 🔍 ID 用字符串
"amount": 123456789, // 数值小,可用 Number
"user_id": "18012345678901234568"
}
(2)状态层:用 BigInt
做计算,但不存进 Redux
js
// calc.js
export function addId(idStr, offset) {
const id = BigInt(idStr);
return (id + BigInt(offset)).toString(); // 🔍 计算完转回字符串
}
(3)渲染层:永远用字符串插值
js
// ✅ 安全渲染
const row = `<tr data-id="${order.id}"> // 🔍 直接用字符串,不转 Number
<td>${order.id}</td>
</tr>`;
应用扩展:可复用的配置片段
1. Axios 自动转换大数字段
js
// axios.interceptor.js
axios.defaults.transformResponse = [
(data, headers) => {
if (headers['content-type']?.includes('json')) {
return JSON.parse(data, (key, value) => {
// 🔍 指定字段转 BigInt
if (['order_id', 'user_id'].includes(key) && /^\d{16,}$/.test(value)) {
return value; // 🔍 保持字符串,由业务层决定是否转 BigInt
}
return value;
});
}
return data;
}
];
2. 环境适配说明
场景 | 注意点 |
---|---|
IE 浏览器 | BigInt 不支持,需降级用 string + bignumber.js |
TypeScript | 类型定义用 bigint 或 string ,别用 number |
JSON 序列化 | BigInt 不能直接 JSON.stringify() ,需自定义 toJSON |
举一反三:3 个变体场景
- 金融计算(高精度小数)
用BigInt
模拟定点数:123.45
存为12345n
(单位:分),运算后再除100
; - 数据库主键生成(Snowflake ID)
前端生成 ID 时用BigInt
拼接时间戳、机器码、序列号,避免重复; - 区块链地址校验
以太坊地址是 256 位整数,用BigInt
做范围校验和签名计算。
小结
别让 Number
碰超过 16 位的数字。
传用字符串,算用 BigInt
,渲染不转 Number
,三招封死精度陷阱。