JSON 超长数字为什么会丢精度:2^53 与雪花 ID
JSON 规范里数字没有精度上限,但绝大多数解析器把它实现成 IEEE 754 双精度浮点,能精确表示的整数上限是 2^53−1(9007199254740991)。超过这个值,末尾几位就再也回不来了——不是四舍五入,是信息直接丢失。
丢在哪一步:解析,不是显示
很多人以为只是"显示时被截断了",其实丢精度发生在 JSON.parse 的那一刻:文本里的 12345678901234567890 被读成 double 后就已经变成 12345678901234567168,后面无论怎么格式化、怎么再序列化,都只能拿到这个新值。
这也是为什么「复制出来看一看」没用——你看的是已经被改过的数。要判断有没有丢,只能比较原始文本与输出文本的字符。
哪些字段最容易中招
- 雪花 ID:19 位十进制,远超 2^53,最常见。
- 订单号 / 流水号:很多系统用时间戳拼序列,也是 18–19 位。
- 银行卡号、身份证号:本该是字符串,一旦被写成数字就会丢位。
- 时间戳(纳秒 / 微秒):19 位整数,同样超限。
行业里的通行做法是把这些字段定义成字符串,从源头避开问题。但如果接口已经返回了数字,你能做的只有换一个保留原始文本的解析器。
怎么验证输出没被改值
拿一个超过 2^53 的整数贴进去,格式化后再看输出:
- 输入
{"id": 12345678901234567890}。 - 点格式化。
- 逐位比较输出里的数字与输入是否完全一致。
一致说明解析器保留了原始文本;如果末尾变成 ...567168 或 ...567000,就是走了 double。这个工具的结果是逐位一致。
自己的代码里怎么避免
JavaScript 侧有两个办法:一是用 JSON.parse 的 reviver 拿不到原始文本(已经晚了),需要改用能保留源文本的解析器;二是在服务端就把大整数序列化成字符串。
更彻底的做法是在接口契约里约定:所有可能超过 2^53 的整数一律用 string。这样前端、移动端、任何语言的客户端都不会踩同一个坑。
上面这些都不用装软件,也不用注册:打开页面就能用。
打开 JSON 在线解析 →常见问题
2^53 到底是多大的数?
9007199254740992(2 的 53 次方)。双精度浮点能精确表示的整数上限是 2^53−1,即 9007199254740991,超过之后相邻两个整数可能对应同一个浮点值。
把数字加引号就安全了吗?
安全,但会改变类型。字段变成字符串后,接收方若按数字处理需要自己转换,且要小心转换时又用了 double。更好的做法是前后端在契约里统一约定这类字段的类型。
小数点后的精度也会丢吗?
会,而且更隐蔽。0.1 + 0.2 这类十进制小数在二进制里无法精确表示,涉及金额时应使用整数分或字符串,不要用浮点。