※この記事は、個人技術ブログ CodeArchPedia.com の技術メモ(要約)です。
どうも、現場で数多くのJavaScriptの数値計算バグを踏んできたシニアエンジニアです。金融系やネットワーク通信のペイロードを扱う際、JavaScriptの標準のNumber型が持つ「符号なし64ビット整数(uint64)」の表現限界について、よく落とし穴にはまりますね。結論から申し上げますが、JavaScriptで uint64 相当の大きな整数を扱う際は、標準のNumber型は使わず、必ず BigInt型を使用してください。これこそが、将来的なバグを防ぐための唯一のベストプラクティスです。
何が起きたか(課題)
JavaScriptの Number 型がIEEE 754の倍精度浮動小数点形式で定義されているため、安全に整数を表現できる範囲に制限があります。安全限界(Number.MAX_SAFE_INTEGER)は 2^53 - 1 ですが、uint64の最大値(約1.84 x 10^19)はこの値を遥かに超えています。
- uint64の最大値(2^64 - 1)を
Number型で扱おうとすると、意図せず丸められて精度が失われる。 - 特に、他言語(C/C++, Go, Javaなど)とのインターフェース部分で、巨大なIDやタイムスタンプの値が破損する。
- 16進数リテラルで uint64 最大値を記述すると、JavaScriptエンジンがそれを
Numberとして解釈し、末尾が丸められた値になる。
どう解決したか(概要)
この問題を解決するために、ECMAScript 2020で導入された BigInt 型を全面的に採用しました。BigInt はメモリが許す限り任意の精度の整数を扱えます。
- uint64値のリテラルを定義する際は、数値の末尾に
nを付与してBigIntとして明示的に定義する。 - 外部(APIなど)から文字列として大きな数値を受け取った場合は、
BigInt()コンストラクタを使用して安全にBigInt型に変換する。 -
BigIntとNumberは混在できないため、全ての関連する演算(比較、計算など)において、Number型を必要に応じてBigIntに変換してから実行する。 - JSONへのシリアライズ時には
BigIntは非対応なため、JSON.stringify実行前に、BigIntを文字列 (.toString()) に変換するカスタムロジックを適用する。
BigInt を使うことで、最大 uint64 値に対しても演算の精度が完全に維持されます。
効果(Before/After)
Number 型を使っていた頃は、大きなIDやペイロード値が知らぬ間に丸められ、システムの一貫性や信頼性が損なわれていました。特に分散IDやトランザクションIDの比較処理で、小さなズレが深刻なバグを引き起こすことがありました。
BigInt 導入後は、uint64の最大値を含め、全ての巨大な整数値が期待通りに正確に保持・計算されるようになりました。これにより、特に外部システムとのデータ連携部分における数値計算バグが根本的に解消され、システムの堅牢性が大幅に向上しました。
| 項目 | Before (Number) |
After (BigInt) |
|---|---|---|
| uint64最大値の表現 | 精度喪失が発生(丸め) | 完全に正確に保持 (18446744073709551615n) |
| 演算の安全性 | 型エラーまたは予期せぬ誤差 | 厳密な型チェックと正確な計算 |
| JSON対応 |
stringifyでエラー |
カスタムロジックで文字列化し対応 |
🚀 詳細な設定とコードはこちら
具体的なWAFのルール設定や、より詳細なログ解析データは元のブログで公開しています。