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

javascrpit JavaScript
スポンサーリンク

BigInt(ビッグイント) は JavaScript の数値扱いで近年とても重要なトピックです。
「普通の数(Number)では足りないほど大きな整数」を正確に扱うために導入されました。
初心者にもわかりやすく、実例つきで丁寧に解説します。

1. BigIntとは?

JavaScript には昔から「Number型」がありましたが、これは内部的に 64ビットの浮動小数点数(IEEE754)で表現されます。
そのため、安全に扱える整数の範囲(=誤差が出ない範囲)が限られています。

Number.MAX_SAFE_INTEGER // 9007199254740991
Number.MIN_SAFE_INTEGER // -9007199254740991
JavaScript

これを超えると、整数なのにズレてしまいます。

9007199254740992 === 9007199254740993  // true 😱 (誤差)
JavaScript

💡 BigInt はこれを解決するために登場しました。
「どんなに大きくても、整数なら正確に扱える」 のが特徴です!

2. BigInt の作り方(リテラル)

作り方は簡単です。
数値の最後に n をつけるだけ!

const big1 = 123456789012345678901234567890n;
const big2 = 42n;
JavaScript

または関数で変換:

const big3 = BigInt(9007199254740991);
const big4 = BigInt("9007199254740992"); // 文字列もOK
JavaScript

3. 基本の演算

普通の数と同じように四則演算ができます。

10n + 20n   // 30n
10n - 3n    // 7n
10n * 5n    // 50n
10n / 3n    // 3n   ← 小数点以下は切り捨て!
10n % 3n    // 1n
JavaScript

注意:

BigInt での割り算は必ず整数になります(小数点以下は自動的に切り捨て)。

4. Number と混ぜて使えない!

BigInt と Number は別の型なので、直接混ぜて計算するとエラーになります。

10n + 5   // ❌ TypeError: Cannot mix BigInt and other types
JavaScript

使いたい場合は変換します:

// Number → BigInt
10n + BigInt(5)  // ✅ 15n

// BigInt → Number
Number(10n) + 5  // ✅ 15
JavaScript

ポイント:

  • BigInt で扱える範囲は「理論上ほぼ無限大」。
  • ただし浮動小数(小数点付き)は扱えません。

5. 比較と等価判定

比較演算子はOK、等価比較は少し注意。

10n > 5n         // true
10n === 10n      // true

10n == 10        // true  (ゆるい等価ならOK)
10n === 10       // false (型が違うため)
JavaScript

おすすめ:

  • 厳密比較(===)では型が異なるので注意。
  • 比較演算子(<, >, <=, >=)は型変換して比較できます。

6. BigIntとMath関数の関係

Math オブジェクトは Number 専用なので、BigInt では使えません。

Math.sqrt(16n);  // ❌ TypeError
JavaScript

代わりに自作関数やBigInt対応ライブラリを使う必要があります。

例:簡単な平方根関数

function bigintSqrt(n) {
  if (n < 0n) throw new Error("Negative BigInt");
  if (n < 2n) return n;
  let x = n;
  let y = (x + 1n) >> 1n; // 平均で近似
  while (y < x) {
    x = y;
    y = (x + n / x) >> 1n;
  }
  return x;
}
bigintSqrt(81n); // => 9n
JavaScript

7. 真偽値変換・条件分岐での扱い

if (0n) console.log("falseにならない"); // 実行されない(0n は falsy)
if (1n) console.log("trueになります");  // 実行される
JavaScript

BigInt は他の値と同様に真偽値へ変換できます。
0n は false、それ以外は true。

8. 実用例(BigIntが役立つ場面)

用途
暗号・ハッシュ処理256ビットなどの大きな整数を扱う
ブロックチェーン(Ethereum, Bitcoin)トークン残高などを正確に管理
精密なIDやタイムスタンプBigInt(Date.now()) など
超大きなファイルサイズやデータ量TB, PB 単位の整数を扱う

例:

const balance = 1234567890123456789012345678901234567890n;
console.log(balance * 2n); 
// 2469135780246913578024691357802469135780n ✅ 精度ロスなし!
JavaScript

9. BigInt の型チェック

typeof 10n;        // "bigint"
typeof BigInt(5);  // "bigint"
JavaScript

10. BigIntとJSON(落とし穴!)

JSON.stringify() ではそのままでは変換できません。

JSON.stringify({ value: 10n }); 
// ❌ TypeError: Do not know how to serialize a BigInt
JavaScript

対処法:

// ① 文字列に変換する
JSON.stringify({ value: 10n.toString() }); // ✅ '{"value":"10"}'

// ② BigInt対応のカスタム関数を使う
JSON.stringify({ value: 10n }, (_, v) => typeof v === "bigint" ? v.toString() : v);
JavaScript

11. まとめ

項目BigIntNumber
内部表現任意長整数64ビット浮動小数点
小数の扱い❌ 不可✅ 可能
安全な整数範囲無限大(メモリ制限のみ)±9,007,199,254,740,991
"bigint""number"
Math関数❌ 非対応✅ 対応
JSON.stringify❌ 直接不可✅ OK
用途暗号、ブロックチェーン、ID生成など一般的な数値計算

12. ちょっとした裏ワザ

➤ BigIntを扱いやすくするヘルパー関数

const toBig = (v) => typeof v === "bigint" ? v : BigInt(v);
const toNum = (v) => typeof v === "number" ? v : Number(v);
JavaScript

➤ 大きなIDを文字列から安全に扱う

const id = BigInt("123456789012345678901234567890");
console.log(id + 1n); // ✅ OK
JavaScript

まとめ(初心者が押さえるべきポイント)

  1. n をつけると BigInt(例:123n
  2. とても大きな整数も正確に扱える
  3. Number と混ぜて計算できない
  4. 割り算は小数点以下切り捨て
  5. JSON.stringify や Math 関数は非対応
  6. 暗号・ブロックチェーン・高精度IDなどで必須

BigIntを使った安全なID生成ツール

/*
BigInt ID Generator
- Compact, collision-resistant ID generator based on BigInt (inspired by Snowflake)
- Features:
  - 64+ bit BigInt ID (timestamp | nodeId | sequence | random)
  - Configurable bit lengths
  - Monotonic within the same instance (sequence increments for same millisecond)
  - Encodes to compact string (base62) and decodes back
  - Safe to use for distributed systems with a nodeId

Usage examples:
const gen = new BigIntIDGenerator({ nodeId: 1 });
const id = gen.next();              // BigInt
const str = gen.nextString();       // base62 string
const parsed = BigIntIDGenerator.parse(id); // {timestamp, nodeId, sequence, random}
const fromStr = BigIntIDGenerator.fromString(str);
*/

// --- Utility: base62 encoding/decoding ---
const BASE62 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
function base62EncodeBigInt(value) {
  if (value === 0n) return '0';
  let v = value < 0n ? -value : value;
  let out = '';
  const base = 62n;
  while (v > 0n) {
    const rem = v % base;
    out = BASE62[Number(rem)] + out;
    v = v / base;
  }
  return value < 0n ? '-' + out : out;
}

function base62DecodeToBigInt(str) {
  if (str === '0') return 0n;
  let negative = false;
  let s = str;
  if (s[0] === '-') { negative = true; s = s.slice(1); }
  let out = 0n;
  const base = 62n;
  for (let i = 0; i < s.length; i++) {
    const idx = BigInt(BASE62.indexOf(s[i]));
    if (idx < 0n) throw new Error('Invalid base62 character: ' + s[i]);
    out = out * base + idx;
  }
  return negative ? -out : out;
}

// --- BigInt ID Generator ---
class BigIntIDGenerator {
  /**
   * options:
   *  epoch: custom epoch in ms (Number). default = 2020-01-01T00:00:00Z
   *  timestampBits: bits reserved for timestamp
   *  nodeIdBits: bits for node id
   *  sequenceBits: bits for sequence counter
   *  randomBits: bits for appended randomness
   *  nodeId: numeric node id (Number or BigInt). If omitted, randomly chosen within range.
   */
  constructor(options = {}) {
    const {
      epoch = Date.UTC(2020, 0, 1), // 2020-01-01
      timestampBits = 42,
      nodeIdBits = 10,
      sequenceBits = 10,
      randomBits = 2,
      nodeId = null,
    } = options;

    // store config
    this.epoch = BigInt(epoch);
    this.timestampBits = timestampBits;
    this.nodeIdBits = nodeIdBits;
    this.sequenceBits = sequenceBits;
    this.randomBits = randomBits;

    // compute shifts and masks
    this.randomMask = (1n << BigInt(randomBits)) - 1n;
    this.sequenceMask = (1n << BigInt(sequenceBits)) - 1n;
    this.nodeIdMask = (1n << BigInt(nodeIdBits)) - 1n;

    this.randomShift = 0n;
    this.sequenceShift = BigInt(randomBits);
    this.nodeShift = BigInt(randomBits + sequenceBits);
    this.timestampShift = BigInt(randomBits + sequenceBits + nodeIdBits);

    // max values
    this.maxNodeId = Number(this.nodeIdMask);

    // node id selection
    if (nodeId === null || nodeId === undefined) {
      // choose a pseudo-random node id in [0, maxNodeId]
      this.nodeId = BigInt(Math.floor(Math.random() * (this.maxNodeId + 1)));
    } else {
      const n = BigInt(nodeId);
      if (n < 0n || n > this.nodeIdMask) throw new RangeError(`nodeId out of range (0..${this.maxNodeId})`);
      this.nodeId = n;
    }

    // runtime state for monotonic sequence
    this.lastTs = -1n;    // last timestamp (ms) seen
    this.sequence = 0n;   // current sequence within same ms
  }

  // --- helpers ---
  _nowMsBigInt() {
    return BigInt(Date.now());
  }

  _waitNextMs(currentTs) {
    // busy-wait until Date.now() advances. Avoids returning duplicate timestamp.
    // Only used when sequence overflows within same ms.
    let ts = this._nowMsBigInt();
    while (ts <= currentTs) {
      ts = this._nowMsBigInt();
    }
    return ts;
  }

  /** produce next ID as BigInt */
  next() {
    const now = this._nowMsBigInt();
    const ts = now - this.epoch;
    if (ts < 0n) throw new Error('Clock before epoch');

    if (now === this.lastTs) {
      // same millisecond -> increment sequence
      this.sequence = (this.sequence + 1n) & this.sequenceMask;
      if (this.sequence === 0n) {
        // overflowed sequence within same ms -> wait for next ms
        const nextTs = this._waitNextMs(now);
        return this._buildId(nextTs - this.epoch, 0n);
      }
    } else {
      // new millisecond -> reset sequence to random small value to reduce collisions
      this.sequence = BigInt(Math.floor(Math.random() * Number(this.sequenceMask + 1n)));
      this.lastTs = now;
    }

    return this._buildId(ts, this.sequence);
  }

  _randomBits() {
    if (this.randomMask === 0n) return 0n;
    return BigInt(Math.floor(Math.random() * Number(this.randomMask + 1n)));
  }

  _buildId(ts, sequence) {
    const tsPart = (BigInt(ts) & ((1n << BigInt(this.timestampBits)) - 1n)) << this.timestampShift;
    const nodePart = (BigInt(this.nodeId) & this.nodeIdMask) << this.nodeShift;
    const seqPart = (BigInt(sequence) & this.sequenceMask) << this.sequenceShift;
    const randPart = (this._randomBits() & this.randomMask) << this.randomShift;

    const id = tsPart | nodePart | seqPart | randPart;
    return id;
  }

  /** produce next ID as base62 string */
  nextString() {
    return base62EncodeBigInt(this.next());
  }

  /** Encode arbitrary BigInt id to base62 string */
  static toString(id) {
    return base62EncodeBigInt(BigInt(id));
  }

  /** Decode base62 string back to BigInt id */
  static fromString(str) {
    return base62DecodeToBigInt(str);
  }

  /** Parse components from an ID (BigInt or base62 string) */
  parseId(value) {
    let id = typeof value === 'string' ? base62DecodeToBigInt(value) : BigInt(value);
    return BigIntIDGenerator.parseWithConfig(id, this);
  }

  /** Static parse given an id and optionally a generator/config */
  static parse(idLike, configLike) {
    const id = typeof idLike === 'string' ? base62DecodeToBigInt(idLike) : BigInt(idLike);
    if (configLike instanceof BigIntIDGenerator) {
      return BigIntIDGenerator.parseWithConfig(id, configLike);
    }
    // if config not provided, assume defaults used in generator constructor
    const temp = new BigIntIDGenerator(configLike || {});
    return BigIntIDGenerator.parseWithConfig(id, temp);
  }

  static parseWithConfig(id, config) {
    const ts = (id >> config.timestampShift);
    const node = (id >> config.nodeShift) & config.nodeIdMask;
    const seq = (id >> config.sequenceShift) & config.sequenceMask;
    const rand = (id >> config.randomShift) & config.randomMask;

    return {
      id: id,
      timestamp: Number(ts + config.epoch), // in ms as Number (may lose precision for extremely large epochs)
      timestampBigInt: ts + config.epoch,
      elapsedSinceEpoch: Number(ts),
      nodeId: node,
      sequence: seq,
      random: rand,
      config: {
        epoch: config.epoch,
        timestampBits: config.timestampBits,
        nodeIdBits: config.nodeIdBits,
        sequenceBits: config.sequenceBits,
        randomBits: config.randomBits,
      }
    };
  }
}

// Export for Node and ESM
if (typeof module !== 'undefined' && module.exports) {
  module.exports = BigIntIDGenerator;
} else {
  window.BigIntIDGenerator = BigIntIDGenerator;
}

// --- Self-test / example (only runs in Node when this file executed directly) ---
if (typeof require !== 'undefined' && require.main === module) {
  (async () => {
    const gen = new BigIntIDGenerator({ nodeId: 1 });
    console.log('nodeId:', gen.nodeId.toString());

    const ids = [];
    for (let i = 0; i < 5; i++) {
      const id = gen.next();
      const s = gen.nextString();
      ids.push({ id, s });
      console.log('BigInt:', id.toString());
      console.log('base62:', s);
      console.log('parsed:', gen.parseId(id));
    }

    // test decode
    const decoded = BigIntIDGenerator.fromString(ids[0].s);
    console.log('decoded equals original:', decoded === ids[0].id);
  })();
}
JavaScript

BigIntとNumberを自動で変換するヘルパー関数

/*
BigInt ID Generator + Conversion Helpers
*/

// --- Utility: base62 encoding/decoding ---
const BASE62 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
function base62EncodeBigInt(value) {
  if (value === 0n) return '0';
  let v = value < 0n ? -value : value;
  let out = '';
  const base = 62n;
  while (v > 0n) {
    const rem = v % base;
    out = BASE62[Number(rem)] + out;
    v = v / base;
  }
  return value < 0n ? '-' + out : out;
}

function base62DecodeToBigInt(str) {
  if (str === '0') return 0n;
  let negative = false;
  let s = str;
  if (s[0] === '-') { negative = true; s = s.slice(1); }
  let out = 0n;
  const base = 62n;
  for (let i = 0; i < s.length; i++) {
    const idx = BigInt(BASE62.indexOf(s[i]));
    if (idx < 0n) throw new Error('Invalid base62 character: ' + s[i]);
    out = out * base + idx;
  }
  return negative ? -out : out;
}

// --- Helper: BigInt <-> Number auto conversion ---
const BigIntHelper = {
  /** Safely converts to Number (with overflow detection) */
  toNumber(value) {
    if (typeof value === 'bigint') {
      const num = Number(value);
      if (BigInt(num) !== value) {
        throw new RangeError('BigInt value too large to fit in Number');
      }
      return num;
    }
    if (typeof value === 'number') return value;
    throw new TypeError('Expected Number or BigInt');
  },

  /** Converts to BigInt safely */
  toBigInt(value) {
    if (typeof value === 'bigint') return value;
    if (typeof value === 'number') {
      if (!Number.isFinite(value) || !Number.isSafeInteger(value)) {
        throw new RangeError('Cannot convert unsafe or non-finite number to BigInt');
      }
      return BigInt(value);
    }
    if (typeof value === 'string') {
      if (/^-?\d+$/.test(value)) return BigInt(value);
      throw new TypeError('Invalid string for BigInt conversion');
    }
    throw new TypeError('Unsupported type for BigInt conversion');
  },

  /** Automatically handles mixed-type arithmetic safely */
  add(a, b) {
    if (typeof a === 'bigint' || typeof b === 'bigint') return this.toBigInt(a) + this.toBigInt(b);
    return a + b;
  },

  subtract(a, b) {
    if (typeof a === 'bigint' || typeof b === 'bigint') return this.toBigInt(a) - this.toBigInt(b);
    return a - b;
  },

  multiply(a, b) {
    if (typeof a === 'bigint' || typeof b === 'bigint') return this.toBigInt(a) * this.toBigInt(b);
    return a * b;
  },

  divide(a, b) {
    if (typeof a === 'bigint' || typeof b === 'bigint') return this.toBigInt(a) / this.toBigInt(b);
    return a / b;
  }
};

// --- BigInt ID Generator ---
class BigIntIDGenerator {
  constructor(options = {}) {
    const {
      epoch = Date.UTC(2020, 0, 1),
      timestampBits = 42,
      nodeIdBits = 10,
      sequenceBits = 10,
      randomBits = 2,
      nodeId = null,
    } = options;

    this.epoch = BigInt(epoch);
    this.timestampBits = timestampBits;
    this.nodeIdBits = nodeIdBits;
    this.sequenceBits = sequenceBits;
    this.randomBits = randomBits;

    this.randomMask = (1n << BigInt(randomBits)) - 1n;
    this.sequenceMask = (1n << BigInt(sequenceBits)) - 1n;
    this.nodeIdMask = (1n << BigInt(nodeIdBits)) - 1n;

    this.randomShift = 0n;
    this.sequenceShift = BigInt(randomBits);
    this.nodeShift = BigInt(randomBits + sequenceBits);
    this.timestampShift = BigInt(randomBits + sequenceBits + nodeIdBits);

    this.maxNodeId = Number(this.nodeIdMask);

    if (nodeId === null || nodeId === undefined) {
      this.nodeId = BigInt(Math.floor(Math.random() * (this.maxNodeId + 1)));
    } else {
      const n = BigInt(nodeId);
      if (n < 0n || n > this.nodeIdMask) throw new RangeError(`nodeId out of range (0..${this.maxNodeId})`);
      this.nodeId = n;
    }

    this.lastTs = -1n;
    this.sequence = 0n;
  }

  _nowMsBigInt() { return BigInt(Date.now()); }

  _waitNextMs(currentTs) {
    let ts = this._nowMsBigInt();
    while (ts <= currentTs) ts = this._nowMsBigInt();
    return ts;
  }

  next() {
    const now = this._nowMsBigInt();
    const ts = now - this.epoch;
    if (ts < 0n) throw new Error('Clock before epoch');

    if (now === this.lastTs) {
      this.sequence = (this.sequence + 1n) & this.sequenceMask;
      if (this.sequence === 0n) {
        const nextTs = this._waitNextMs(now);
        return this._buildId(nextTs - this.epoch, 0n);
      }
    } else {
      this.sequence = BigInt(Math.floor(Math.random() * Number(this.sequenceMask + 1n)));
      this.lastTs = now;
    }

    return this._buildId(ts, this.sequence);
  }

  _randomBits() {
    if (this.randomMask === 0n) return 0n;
    return BigInt(Math.floor(Math.random() * Number(this.randomMask + 1n)));
  }

  _buildId(ts, sequence) {
    const tsPart = (BigInt(ts) & ((1n << BigInt(this.timestampBits)) - 1n)) << this.timestampShift;
    const nodePart = (BigInt(this.nodeId) & this.nodeIdMask) << this.nodeShift;
    const seqPart = (BigInt(sequence) & this.sequenceMask) << this.sequenceShift;
    const randPart = (this._randomBits() & this.randomMask) << this.randomShift;

    return tsPart | nodePart | seqPart | randPart;
  }

  nextString() { return base62EncodeBigInt(this.next()); }

  static toString(id) { return base62EncodeBigInt(BigInt(id)); }

  static fromString(str) { return base62DecodeToBigInt(str); }

  parseId(value) {
    let id = typeof value === 'string' ? base62DecodeToBigInt(value) : BigInt(value);
    return BigIntIDGenerator.parseWithConfig(id, this);
  }

  static parse(idLike, configLike) {
    const id = typeof idLike === 'string' ? base62DecodeToBigInt(idLike) : BigInt(idLike);
    if (configLike instanceof BigIntIDGenerator) return BigIntIDGenerator.parseWithConfig(id, configLike);
    const temp = new BigIntIDGenerator(configLike || {});
    return BigIntIDGenerator.parseWithConfig(id, temp);
  }

  static parseWithConfig(id, config) {
    const ts = (id >> config.timestampShift);
    const node = (id >> config.nodeShift) & config.nodeIdMask;
    const seq = (id >> config.sequenceShift) & config.sequenceMask;
    const rand = (id >> config.randomShift) & config.randomMask;

    return {
      id,
      timestamp: BigIntHelper.toNumber(ts + config.epoch),
      timestampBigInt: ts + config.epoch,
      elapsedSinceEpoch: BigIntHelper.toNumber(ts),
      nodeId: node,
      sequence: seq,
      random: rand,
      config: {
        epoch: config.epoch,
        timestampBits: config.timestampBits,
        nodeIdBits: config.nodeIdBits,
        sequenceBits: config.sequenceBits,
        randomBits: config.randomBits,
      }
    };
  }
}

if (typeof module !== 'undefined' && module.exports) {
  module.exports = { BigIntIDGenerator, BigIntHelper };
} else {
  window.BigIntIDGenerator = BigIntIDGenerator;
  window.BigIntHelper = BigIntHelper;
}
JavaScript

JavaScript | MDN
JavaScript (JS) は軽量でインタープリター型(あるいは実行時コンパイルされる)第一級関数を備えたプログラミング言語です。ウェブページでよく使用されるスクリプト言語として知られ、多くのブラウザー以外の環境、例えば Node.js...
タイトルとURLをコピーしました