お疲れ。最近、金融系のダッシュボードや、やたらと大きなIDを扱うバックエンドAPIとの連携で、`Number`型の精度落ち(Precision Loss)に頭を悩ませていないか?
「なんかフロントエンドで受け取ったIDの末尾が勝手に `0` に丸められるんだけど……」なんてバグを踏んだことがあるなら、君も立派な中級エンジニアの仲間入りだ。
JavaScriptにおける巨大整数の救世主、それが`BigInt`だ。だがな、この`BigInt`、いざ実務で使おうとすると、そこかしこに「地雷」が埋まっている曲者なんだよ。今日は、ブラウザの裏側の事情から実務でのハックまで、シニアの俺が徹底的に叩き込んでやる。心して聞け。
—
1. なぜ`BigInt`が必要なのか?(Number型の限界とブラウザの裏側)
まず大前提として、JavaScriptの通常の数値を表す`Number`型は、IEEE 754規格に基づく「倍精度浮動小数点数(64ビット)」で表現されている。
これの何が問題かと言うと、整数を正確に表現できる限界値(安全な整数)が `Number.MAX_SAFE_INTEGER`(すなわち `2^53 – 1`、つまり `9,007,199,255,164,791`)に固定されている点だ。これを超える数値を`Number`で扱うと、ブラウザのCPU(V8などのJSエンジン)は下位のビットを勝手に丸めやがる。データベースのAuto IncrementのIDがこの桁数を超えた瞬間、データが破損する大惨事の出来上がりだ。
そこでES2020で登場したのが`BigInt`だ。`BigInt`は、メモリ上で可変長(あるいは十分なビット数)の領域を確保し、理論上、メモリが許す限り無限の桁数の整数を正確に保持できる。
// Number型の限界を超える例
const unsafeNumber = 9007199255164792n; // 末尾の ‘n’ が BigInt リテラル
console.log(unsafeNumber); // 9007199255164792n (正確に保持される)
const normalNumber = 9007199255164792;
console.log(normalNumber); // 9007199255164790 (勝手に丸められる!地獄の始まり)
—
2. 実務で絶対に踏むな!`BigInt`の3大制約と対策
「じゃあ、安全のために全部 `BigInt` に置き換えればいいじゃん?」と思ったそこの君。甘い。実務ではそう単純にはいかない。ここからが本題だ。
制約①:`Number`型との混在計算は「TypeError」で即死する
JSエンジンは、型安全に対して比較的おせっかいな言語だが、暗黙の型変換(Implicit Coercion)の緩さで有名だよな。しかし、`BigInt`はこの暗黙の型変換を完全に拒絶する。`Number`と`BigInt`を混ぜて計算しようものなら、容赦なく`TypeError`を吐いてスクリプトがクラッシュする。
const bigTotal = 100n;
const taxRate = 0.1; // こいつは Number 型
try {
// 混在計算を試みる
const result = bigTotal taxRate;
} catch (e) {
console.error(e.message);
// 出力: TypeError: Cannot mix BigInt and other types, use explicit conversions
}
【現場のベストプラクティス】
計算する前に、明示的にどちらかの型に統一しろ。ただし、`Number`に変換すると精度の意味がなくなることが多いので、基本的にはすべて`BigInt`側に寄せるか、計算が終わるまで型を混ぜないのが鉄則だ。
const bigTotal = 100n;
// 税率を「10」と「100」のBigIntに分解して計算し、最後に割る
const taxMultiplier = 10n;
const baseDivisor = 100n;
const taxAmount = (bigTotal taxMultiplier) / baseDivisor;
console.log(taxAmount); // 10n (綺麗に整数演算できる)
—
制約②:JSONシリアライズ(`JSON.stringify`)で盛大にぶっ壊れる
API通信で避けて通れないのがJSONだ。しかし、JSONの仕様(RFC 8259)には、そもそも`BigInt`という概念が存在しない。そのため、何も考えずに`JSON.stringify()`に`BigInt`を放り込むと、シニア泣かせのエラーが発生する。
const payload = {
id: 9007199255164792345n,
name: “山田太郎”
};
try {
JSON.stringify(payload);
} catch (e) {
console.error(e.message);
// 出力: TypeError: Do not know how to serialize a BigInt
}
【現場のベストプラクティス】
バックエンドから巨大なIDが文字列(String)ではなく数値としてJSONで飛んできた場合のフロントエンド側でのパースや、送信時のハンドリングには工夫が必要だ。送信時はあらかじめ文字列に変換するのが一番安全な布陣と言える。
// 送信時のシリアライズ対策:replacer関数を使うか、事前に文字列に変換する
const safeStringify = (obj) => {
return JSON.stringify(obj, (key, value) =>
typeof value === ‘bigint’ ? value.toString() : value
);
};
console.log(safeStringify(payload));
// 出力: {“id”:”9007199255164792345″,”name”:”山田太郎”}
// これならサーバー側も安全に受け取れる。
—
制約③:Mathオブジェクトのメソッドが使えない
「最大値を求めたい」「絶対値を出したい」と思って、お馴染みの`Math.max()`や`Math.floor()`に`BigInt`を渡すと、これまた`TypeError`の餌食になる。`Math`オブジェクトは内部で`Number`型を前提に最適化されているからだ。
const a = 10n;
const b = 20n;
// Math.max(a, b); // TypeError: Cannot convert a BigInt value to a number
【現場のベストプラクティス】
単純な比較演算子(`>` や `<`)は`BigInt`同士でも問題なく動くので、自前で比較関数を書くか、三項演算子で処理しろ。
// BigInt用の簡易的な max 関数
const bigMax = (x, y) => (x > y ? x : y);
console.log(bigMax(a, b)); // 20n
—
3. 実務でそのまま使える!ユーティリティスニペット
最後に、現場でAPIレスポンス(特にJSONのパース時や、巨大IDを扱うフォームなど)で役立つ、実用的なユーティリティコードを置いておく。コピペしてプロジェクトの `utils/` あたりに放り込んでおくといい。
/
- オブジェクト内の特定のキー、またはすべてのBigIntを文字列に安全に変換する
- @param {any} data
- @returns {any}
/
export function serializeBigInts(data) {
if (data === null || typeof data !== ‘object’) {
return typeof data === ‘bigint’ ? data.toString() : data;
}
if (Array.isArray(data)) {
return data.map(serializeBigInts);
}
const result = {};
for (const key of Object.keys(data)) {
result[key] = serializeBigInts(data[key]);
}
return result;
}
/
- フォーム入力値(文字列)を安全にBigIntに変換する(バリデーション付き)
- @param {string} value
- @returns {bigint | null}
/
export function parseSafeBigInt(value) {
if (!value || typeof value !== ‘string’) return null;
// 数字以外の文字が含まれていないか厳密にチェック
if (!/^\d+$/.test(value.trim())) {
console.warn(`無効なBigInt文字列です: “${value}”`);
return null;
}
try {
return BigInt(value);
} catch (e) {
console.error(‘BigIntへの変換に失敗しました:’, e);
return null;
}
}
—
まとめ
`BigInt`は、精度の呪縛から私たちを解放してくれる強力な武器だ。しかし、暗黙の型変換の効かなさや、JSON・Mathオブジェクトとの相性の悪さなど、「通常のNumber型と同じ感覚では使えない」という牙を隠している。
仕様の裏側にある「なぜ型を混ぜられないのか(異なるメモリ構造と演算処理のため)」「なぜJSONでシリアライズできないのか」を理解していれば、エラーに怯える必要はない。
しっかりと特性を把握し、スマートに巨大データをハンドリングできるワンランク上のフロントエンドエンジニアを目指してくれ。応援しているぞ!

コメント