JSON 工具箱

大整數為什麼變成 9007199254740992

結論:超過 2^53-1 的整數在 IEEE 754 雙精度裡無法精確表示,JSON.parse 會靜默把末位改掉。訂單號、雪花 ID、金額都踩過這個坑,正確解法是全程當字串傳。

進階約 4 分鐘發佈於 2026-10-30更新於 2026-11-04
大整數在 JSON 解析中精度遺失的原因與解法

結論:把雪花演算法 ID、訂單號、bigint 主鍵這類識別碼當作字串,從序列化、傳輸到解析全程不進入數字通道;只有在確認欄位不會超過 2^53-1 時,才可以保留數字型別並在 Schema 裡加 maximum 約束。

因為解析它的那一刻,精度就已經丟了,而且不會報錯。

JSON.parse('{"id": 9007199254740993}');
// { id: 9007199254740992 }

少掉的那一位,找不回來了。

2^53 這條線怎麼來的

JavaScript 的 number 只有一種:IEEE 754 雙精度浮點,用 53 位表示有效數字(1 位隱含 + 52 位顯式),能精確表示的整數範圍是:

-9007199254740991 ~ 9007199254740991   (即 ±(2^53-1))

超過這條線,相鄰兩個整數之間的距離不再等於 1,而是 2、4、8…… 具體落到哪個值取決於二進位表示。9007199254740993 的末位是 1,而這個量級上能被表示的數末位都是 0,於是它只能進位到 9007199254740992。

哪些欄位最容易中招

欄位 典型位數 風險
雪花演算法 ID 18–19 位 高,末幾位直接錯
訂單號、流水號 16–20 位 高
資料庫 bigint 主鍵 19 位 高
金額(以分為單位) 一般安全 低
時間戳(毫秒) 13 位 安全

共同點:它們是識別碼,不是拿來做算術的數字。識別碼只需要原樣搬運,所以最合適的型別是字串。

四種解法

一、序列化時就轉字串。 後端在出參前把 bigint 欄位轉成 string,前端按字串處理。最省事也最可靠,代價是要在介面文件寫清楚「這些欄位是字串」。

二、讓序列化函式庫處理。 Java 的 Jackson 可以給 Long 配 ToStringSerializer;Go 用 json:",string" 標籤。核心都是「不讓它進數字通道」。

三、前端用 BigInt 解析。 JSON.parse 本身不支援,需要 json-bigint 之類的函式庫。注意 BigInt 不能和 Number 混算,JSON.stringify 也不認它。

四、約定安全範圍。 如果欄位確實不會超過 2^53,在 Schema 裡加 maximum 約束,讓越界資料在校驗階段就失敗。

怎麼驗證自己有沒有踩坑

拿一個已知的 ID,走一遍「序列化 → 傳輸 → 解析」,比對前後是否逐字相等。抓包看原始回應體:如果回應裡是沒有引號的長數字,而前端拿到的末位不對,基本可以確定是解析階段丟的精度。

常見問題

為什麼 JSON.parse 改了末位卻不報錯?

JavaScript 的 number 只有 IEEE 754 雙精度一種型別,超出 2^53-1 的整數只能落到附近的近似值上。JSON.parse 把這一步當成正常的數值轉換,既不拋錯也不提示。

前端能否用 BigInt 保住大整數?

可以,但 JSON.parse 本身不支援,要換成 json-bigint 這類函式庫。代價是 BigInt 不能和 Number 混算,JSON.stringify 也不認它,後續處理要跟著改。

要不要在介面文件裡標明這些欄位是字串?

要。序列化時轉字串最省事也最可靠,但前提是消費方能按字串處理,所以欄位型別必須在介面文件裡寫死,否則前端仍會把它當數字用。

延伸閱讀