「BigInt を使った具体的なユースケース(大きな ID を生成する、安全な台帳計算など)のサンプル実装」
BigInt を使った実用サンプルを2つ用意。どちらもすぐ動かせる Node.js / ブラウザ(コンソール)向けのシンプル実装で、使い方・注意点・テスト例付きです。
- 大きな ID を生成する
bigIdGenerator(単純・衝突低減のためタイムスタンプ+カウンタ+ランダムを BigInt で合成) - 金額や台帳向けの
bigLedger(BigInt を使った安全な加算・減算・合計・JSON入出力のユーティリティ)
1) 大きな ID を生成する — bigIdGenerator.js
目的:分散環境で一意性を高めつつ、桁数が非常に大きくても安全に扱える整数 ID を生成する。
設計方針(単純版):
- 秒またはミリ秒タイムスタンプ(
Date.now())を主成分 - 短時間の衝突を避けるためのローカルカウンタ
- 小さなランダム値を付与
- すべてをビットシフト/加算で合成(BigInt)
// bigIdGenerator.js
// Node / Browser で動作(BigInt をサポートする環境が前提)
const BigIdGenerator = (() => {
// ビット幅の設計(例):
// 48 bits for timestamp (ms since epoch modulo something)
// 12 bits for counter (4096 values per ms)
// 16 bits for randomness
// 合計: 76 bits など(必要に応じて拡張)
const RANDOM_BITS = 16n;
const COUNTER_BITS = 12n;
const TIMESTAMP_SHIFT = COUNTER_BITS + RANDOM_BITS;
const COUNTER_MAX = (1n << COUNTER_BITS) - 1n;
let lastTs = 0n;
let counter = 0n;
function nowMs() {
return BigInt(Date.now());
}
function next() {
let ts = nowMs();
if (ts === lastTs) {
counter = counter + 1n;
if (counter > COUNTER_MAX) {
// カウンタが回りきった場合は次ミリ秒を待つ(簡易実装)
// ※ブロッキング待ちは避け、ループでミリ秒進むのを待つ
while (nowMs() <= lastTs) { /* busy-wait; 運用では setTimeout 等で待つ実装が望ましい */ }
ts = nowMs();
counter = 0n;
}
} else {
counter = 0n;
lastTs = ts;
}
// randomness: 0 <= r < 2**RANDOM_BITS
const rand = BigInt(Math.floor(Math.random() * Number(1 << Number(RANDOM_BITS))));
// 組み立て: (ts << TIMESTAMP_SHIFT) | (counter << RANDOM_BITS) | rand
const id = (ts << TIMESTAMP_SHIFT) | (counter << RANDOM_BITS) | rand;
return id; // BigInt
}
function toHex(id) {
return "0x" + id.toString(16);
}
function toDecString(id) {
return id.toString(10);
}
return { next, toHex, toDecString };
})();
// 使い方
const id1 = BigIdGenerator.next();
console.log("ID (dec):", BigIdGenerator.toDecString(id1));
console.log("ID (hex):", BigIdGenerator.toHex(id1));
JavaScriptポイント / 注意
Date.now()を使っているため、マシン間で時計がずれていると ID のソート順が狂います。NTP 等で時計合わせが望ましい。- カウンタ満杯の扱いは簡素化(busy-wait)。実運用では非同期の待機や別の熾烈回避手段が必要。
- 必要なら桁幅(bit 割当)を増やして衝突リスクを下げる。BigInt なら桁数問題は気にせず拡張可能。
- ID を JSON で送る場合は
id.toString()して文字列で送る(JSON.stringifyは BigInt を直接扱えない)。
2) 台帳(会計)向け bigLedger.js
目的:金額(最小通貨単位で整数化)を BigInt で安全に合算・差し引きする。
設計方針:
- 通貨は最小単位(例:JPY → 円単位、USD → セント)で整数化して BigInt で扱う
- 入出力(API/JSON)では文字列化してやり取り(
JSON.stringifyでエラーにならないように) - 丸めや分配、集計のユーティリティを用意
// bigLedger.js
// amount: BigInt (最小単位で渡す)
// Entry example: { id: "txn-001", amount: 1200n, type: "credit", meta: {...} }
class BigLedger {
constructor() {
this.entries = []; // array of {id, amount: BigInt, type}
}
// 追加(負の金額も許容)
addEntry(entry) {
if (typeof entry.amount !== "bigint") {
throw new TypeError("entry.amount must be a BigInt (smallest currency unit).");
}
this.entries.push(entry);
}
// 総計を求める(BigInt を返す)
getBalance() {
// すべての amount を単純合算
return this.entries.reduce((acc, e) => acc + e.amount, 0n);
}
// 合計(credit と debit を分けたい場合)
getTotalsByType() {
return this.entries.reduce((acc, e) => {
acc[e.type] = (acc[e.type] || 0n) + e.amount;
return acc;
}, {});
}
// JSON へ安全に変換(BigInt を文字列に置換)
toJSON() {
return JSON.stringify(this.entries.map(e => ({
...e,
amount: e.amount.toString()
})));
}
// JSON 文字列を読み込み(amount は BigInt に戻す)
static fromJSON(jsonStr) {
const arr = JSON.parse(jsonStr);
const ledger = new BigLedger();
for (const e of arr) {
ledger.addEntry({ ...e, amount: BigInt(e.amount) });
}
return ledger;
}
}
// 使い方サンプル
const ledger = new BigLedger();
ledger.addEntry({ id: "t1", amount: 10000n, type: "credit" }); // 100.00 (通貨最小単位依存)
ledger.addEntry({ id: "t2", amount: -2500n, type: "debit" });
ledger.addEntry({ id: "t3", amount: 350n, type: "credit" });
console.log("Balance (smallest unit):", ledger.getBalance().toString()); // BigInt を文字列化して表示
console.log("Totals by type:", ledger.getTotalsByType());
// JSON 安全化
const json = ledger.toJSON();
console.log("Ledger JSON:", json);
const loaded = BigLedger.fromJSON(json);
console.log("Loaded balance:", loaded.getBalance().toString());
JavaScriptポイント / 運用注意
- 台帳では 必ず最小通貨単位で整数化(例:ドルなら cents)して
BigIntにする。浮動小数点で金額を扱うと誤差の原因。 - 外部 API とやり取りするときは
Stringにして送る(toString())。受け取った側でBigInt()に戻す。 - 分割やパーセンテージ配分を行う場合は、丸め(端数処理)ルールを明文化(切り捨て/四捨五入/銀行丸め)して実装。BigInt の除算は切り捨てなので、端数配分ロジックを追加する必要あり。
例:配分(全体のうち n 人に均等に配分し、残余を最初の人に付与する簡単な実装)
function distribute(total, n) {
// total: BigInt (最小単位)
// n: integer > 0
const base = total / BigInt(n);
const remainder = total % BigInt(n);
const result = Array.from({ length: n }, (_, i) => base + (i < Number(remainder) ? 1n : 0n));
return result;
}
JavaScript追加ユーティリティ:BigInt と Number を扱うヘルパー
// safeConvert.js
function ensureBigInt(x) {
if (typeof x === "bigint") return x;
if (typeof x === "number") {
if (!Number.isFinite(x) || !Number.isInteger(x)) {
throw new TypeError("Cannot convert non-integer or non-finite number to BigInt safely.");
}
return BigInt(x);
}
if (typeof x === "string") return BigInt(x);
throw new TypeError("Unsupported type for conversion to BigInt");
}
// 安全に加算(自動変換ルール)
function safeAdd(a, b) {
if (typeof a === "bigint" || typeof b === "bigint") {
return ensureBigInt(a) + ensureBigInt(b);
} else {
return a + b; // both numbers
}
}
JavaScript注意:ensureBigInt(Number) は Number が大きすぎると精度損失している可能性があるため、そのリスクを承知の上で使ってください。
テスト・確認方法(すぐ試せる)
- Node.js(v10.4+ で BigInt サポート)やブラウザコンソールでファイルを読み込み、上記のサンプルを実行して動作確認してください。
- JSON の送受信は
toString()→BigInt()の往復で問題回避できます。
最後に(推奨)
- 実運用で金融系や台帳を扱うなら、業務ルールに従った端数処理・監査ログ・耐障害設計が必須です。BigInt は「数値精度」の問題を解く道具ですが、業務的な正しさ(丸めルール、通貨換算、トランザクション整合性)は別途設計してください。
- もっと洗練された ID を作りたい(分散ノードで順序保証したい、UUID 互換が欲しい等)なら、Snowflake 方式や ULID の BigInt バージョンなどの実装例を作れます。

