JavaScript | 長整数リテラルの記述方法

JavaScript JavaScript
スポンサーリンク

「BigInt を使った具体的なユースケース(大きな ID を生成する、安全な台帳計算など)のサンプル実装」

BigInt を使った実用サンプルを2つ用意。どちらもすぐ動かせる Node.js / ブラウザ(コンソール)向けのシンプル実装で、使い方・注意点・テスト例付きです。

  1. 大きな ID を生成する bigIdGenerator(単純・衝突低減のためタイムスタンプ+カウンタ+ランダムを BigInt で合成)
  2. 金額や台帳向けの 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 バージョンなどの実装例を作れます。
タイトルとURLをコピーしました