JSON Toolkit

大きな整数が精度を失う理由

要点:2^53-1 を超える整数は JSON.parse で精度を失います。スノーフレーク ID、注文番号、データベースの bigint がすべて該当するので、端から端まで文字列で保ちましょう。

中級約 4 分で読めます2026-10-30 公開2026-11-04 更新
JSON で大きな整数が精度を失う理由と保ち方

要点:スノーフレーク ID、注文番号、bigint 主キーはシリアライズ、転送、パースを通じて文字列として扱いましょう。数値型を保つのは、フィールドが 2^53-1 以内に収まると証明でき、Schema の maximum 制約で強制できる場合だけです。

パース中に、静かに損傷が起きるからです。

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

失われた桁は消えており、回復するものは何もありません。

2^53 はどこから来るか

JavaScript の数値型は一つだけです。IEEE 754 倍精度で、仮数部は 53 ビット(1 ビットが暗黙、52 ビットが明示)。正確な整数はしたがって ±(2^53-1) で止まります。

-9007199254740991 .. 9007199254740991

その線を超えると、隣り合う表現可能値の間隔は 2、4、8 になります。9007199254740993 は奇数で、その大きさでは表現可能な値がすべて偶数なので、9007199254740992 に丸められます。

影響を受けるフィールド

フィールド 桁数 リスク
スノーフレーク ID 18〜19 高い。末尾の桁が変わる
注文 / 取引番号 16〜20 高い
データベースの bigint 主キー 19 高い
最小単位の金額 — 低い
ミリ秒タイムスタンプ 13 安全

共通するのは、これらは識別子であり、量ではないことです。識別子は運ばれるだけで、加算も乗算もされないので、文字列が正しい型です。

四つの修正

文字列としてシリアライズする。 バックエンドがレスポンスを書く前に bigint フィールドを変換します。単純で持続的。これらのフィールドが文字列であることを文書化します。

シリアライザに任せる。 Jackson の Long 用 ToStringSerializer、Go の json:",string" タグ。発想は同じで、値を数値の経路から外します。

BigInt 対応でパースする。 json-bigint などのライブラリ。BigInt は Number の算術と混ぜられず、JSON.stringify が拒否することに注意してください。

範囲を制約する。 フィールドが本当に 2^53 を超えられないなら、Schema に maximum を足し、範囲外のデータがブラウザで静かに失敗するのではなく検証で失敗するようにします。

このバグがあるかどうかの見分け方

既知の ID を往復させます。シリアライズ、転送、パース、文字単位で比較。または生のレスポンスボディを調べます。引用符なしの 19 桁の数値が誤った末尾桁で届くなら、ほぼ確実にパース時の損失です。

FAQ

JSON.parse がエラーなしで末尾の桁を変えるのはなぜですか?

JavaScript の数値型は IEEE 754 倍精度の一つだけなので、2^53-1 を超える整数は最も近い表現可能値にしか落ちません。JSON.parse はそれを通常の数値変換として扱い、例外も警告もしません。

フロントエンドで大きな整数をそのまま保つには?

json-bigint のようなライブラリを使います。JSON.parse ではできません。コストは BigInt が Number の算術と混ざらず JSON.stringify が拒否することなので、周囲のコードも変える必要があります。

API ドキュメントにこれらのフィールドが文字列だと書くべきですか?

はい。文字列へのシリアライズは最も単純で持続的な修正ですが、消費者が値をテキストとして扱って初めて機能するので、フィールド型を API 契約に書き込む必要があります。

さらに読む