JavaScript for impatient programmers (ES2022版)
この書籍をサポートしてください:購入 または 寄付
(広告、ブロックしないでください。)

18章 BigInt – 任意精度整数 [ES2020] (上級)



この章では、必要に応じてストレージサイズが増減するJavaScriptの整数型であるBigIntについて見ていきます。

18.1章 BigIntを使う理由

ECMAScript 2020以前、JavaScriptは整数を次のように扱っていました。

場合によっては、符号付き53ビットを超える必要があることがあります。例えば

18.2章 BigInt

BigIntは、整数のための新しいプリミティブデータ型です。BigIntはビット数に固定されたストレージサイズを持たず、表現する整数に合わせてサイズが調整されます。

BigIntリテラルは、1つ以上の数字のシーケンスで、`n`で終わります。例えば

123n

`-`や`*`などの演算子はオーバーロードされており、BigIntでも動作します。

> 123n * 456n
56088n

BigIntはプリミティブ値です。`typeof`はそれらに対して新しい結果を返します。

> typeof 123n
'bigint'

18.2.1節 53ビットを超える整数

JavaScriptの数値は内部的には、分数に指数を掛けたものとして表現されます(詳細については、§16.8「背景:浮動小数点精度」を参照)。その結果、最大の安全な整数 253−1を超えると、表現できる整数はまだありますが、それらの間には隙間があります。

> 2**53 - 2 // safe
9007199254740990
> 2**53 - 1 // safe
9007199254740991

> 2**53 // unsafe, same as next integer
9007199254740992
> 2**53 + 1
9007199254740992
> 2**53 + 2
9007199254740994
> 2**53 + 3
9007199254740996
> 2**53 + 4
9007199254740996
> 2**53 + 5
9007199254740996

BigIntを使用すると、53ビットを超えることができます。

> 2n**53n
9007199254740992n
> 2n**53n + 1n
9007199254740993n
> 2n**53n + 2n
9007199254740994n

18.2.2節 例:BigIntの使用

BigIntの使い方は次のとおりです(提案の例に基づいています)。

/**
 * Takes a bigint as an argument and returns a bigint
 */
function nthPrime(nth) {
  if (typeof nth !== 'bigint') {
    throw new TypeError();
  }
  function isPrime(p) {
    for (let i = 2n; i < p; i++) {
      if (p % i === 0n) return false;
    }
    return true;
  }
  for (let i = 2n; ; i++) {
    if (isPrime(i)) {
      if (--nth === 0n) return i;
    }
  }
}

assert.deepEqual(
  [1n, 2n, 3n, 4n, 5n].map(nth => nthPrime(nth)),
  [2n, 3n, 5n, 7n, 11n]
);

18.3章 BigIntリテラル

数値リテラルと同様に、BigIntリテラルはいくつかの基数をサポートしています。

負のBigIntは、単項マイナス演算子を前に付けることで生成されます:`-0123n`

18.3.1節 BigIntリテラルにおける下線(_)によるセパレータ [ES2021]

数値リテラルと同様に、BigIntリテラルでも下線(_)をセパレータとして使用できます。

const massOfEarthInKg = 6_000_000_000_000_000_000_000_000n;

BigIntは、金融技術分野で金額を表すために頻繁に使用されます。ここでセパレータも役立ちます。

const priceInCents = 123_000_00n; // 123 thousand dollars

数値リテラルと同様に、2つの制限が適用されます。

18.4章 BigIntのための数値演算子の再利用(オーバーロード)

ほとんどの演算子では、BigIntと数値を混在させることは許可されていません。混在させると、例外がスローされます。

> 2n + 1
TypeError: Cannot mix BigInt and other types, use explicit conversions

この規則の理由は、数値とBigIntを共通の型に強制変換する一般的な方法がないためです。数値は53ビットを超えるBigIntを表すことができず、BigIntは分数を表すことができません。したがって、例外は予期しない結果につながる可能性のあるタイプミスについて警告します。

例えば、次の式の結果は`9007199254740993n`ですか、それとも`9007199254740992`ですか?

2**53 + 1n

次の式の結果も不明です。

2n**53n * 3.3

18.4.1節 算術演算子

2項`+`、2項`-`、`*`、`**`は期待通りに動作します。

> 7n * 3n
21n

BigIntと文字列を混在させることは問題ありません。

> 6n + ' apples'
'6 apples'

`/`、`%`はゼロに向かって丸めます(`Math.trunc()`と同様)。

> 1n / 2n
0n

単項`-`は期待通りに動作します。

> -(-64n)
64n

多くのコードが被演算子を数値に変換するために単項`+`に依存しているため、BigIntではサポートされていません。

> +23n
TypeError: Cannot convert a BigInt value to a number

18.4.2節 比較演算子

比較演算子`<`、`>`、`>=`、`<=`は期待通りに動作します。

> 17n <= 17n
true
> 3n > -1n
true

BigIntと数値の比較にはリスクがないため、BigIntと数値を混在させることができます。

> 3n > -1
true

18.4.3節 ビット演算子

18.4.3.1節 数値のビット演算子

ビット演算子は、数値を32ビット整数として解釈します。これらの整数は符号なしまたは符号付きです。符号付きの場合、整数の負数はその2の補数です(オーバーフローを無視して整数に2の補数を追加すると、0になります)。

> 2**32-1 >> 0
-1

これらの整数は固定サイズであるため、最上位ビットは符号を示します。

> 2**31 >> 0 // highest bit is 1
-2147483648
> 2**31 - 1 >> 0 // highest bit is 0
2147483647
18.4.3.2節 BigIntのビット演算子

BigIntの場合、ビット演算子は負の符号を無限の2の補数として解釈します。例えば

つまり、負の符号は、実際のビットとして表現されるのではなく、外部フラグのようなものです。

18.4.3.3節 ビット反転(`~`)

ビット反転(`~`)はすべてのビットを反転します。

> ~0b10n
-3n
> ~0n
-1n
> ~-2n
1n
18.4.3.4節 2項ビット演算子(`&`、`|`、`^`)

BigIntに2項ビット演算子を適用する方法は、数値に適用する場合と同様です。

> (0b1010n |  0b0111n).toString(2)
'1111'
> (0b1010n &  0b0111n).toString(2)
'10'

> (0b1010n | -1n).toString(2)
'-1'
> (0b1010n & -1n).toString(2)
'1010'
18.4.3.5節 ビットシフト演算子(`<<`と`>>`)

BigIntの符号付きシフト演算子は、数値の符号を保持します。

> 2n << 1n
4n
> -2n << 1n
-4n

> 2n >> 1n
1n
> -2n >> 1n
-1n

`-1n`は左に無限に伸びる1のシーケンスであることを思い出してください。そのため、左にシフトしても変わりません。

> -1n >> 20n
-1n
18.4.3.6節 ビット右シフト演算子(`>>>`)

BigIntには符号なし右シフト演算子はありません。

> 2n >>> 1n
TypeError: BigInts have no unsigned right shift, use >> instead

なぜですか? 符号なし右シフトの考え方は、0が「左から」シフトされることです。つまり、有限の数の2進数が存在するという仮定に基づいています。

しかし、BigIntには「左」がありません。その2進数は無限に伸びています。これは負の数で特に重要です。

符号付き右シフトは、無限の桁数でも機能します。なぜなら、最上位桁が保持されるからです。したがって、BigIntに適合させることができます。

18.4.4節 ゆるい等価性(==)と不等価性(!=

ゆるい等価性(==)と不等価性(!=)は値を強制変換します。

> 0n == false
true
> 1n == true
true

> 123n == 123
true

> 123n == '123'
true

18.4.5節 厳密な等価性(===)と不等価性(!==

厳密な等価性(===)と不等価性(!==)は、型が同じ場合にのみ値を等しいとみなします。

> 123n === 123
false
> 123n === 123n
true

18.5章 ラッパーコンストラクタ `BigInt`

数値と同様に、BigIntには関連付けられたラッパーコンストラクタ`BigInt`があります。

18.5.1節 コンストラクタと関数としての`BigInt`

表13:値をBigIntに変換する
x BigInt(x)
undefined `TypeError`をスロー
null `TypeError`をスロー
boolean `false` → `0n`、`true` → `1n`
number 例:`123` → `123n`
非整数 → `RangeError`をスロー
bigint x(変更なし)
string 例:`'123'` → `123n`
構文解析できない場合 SyntaxError をスローします
シンボル `TypeError`をスロー
オブジェクト 構成可能 (例: .valueOf() を介して)
18.5.1.1 undefinednull の変換

xundefined または null のいずれかの場合、TypeError がスローされます。

> BigInt(undefined)
TypeError: Cannot convert undefined to a BigInt
> BigInt(null)
TypeError: Cannot convert null to a BigInt
18.5.1.2 文字列の変換

文字列が整数を表していない場合、BigInt()SyntaxError をスローします (一方、Number() はエラー値 NaN を返します)。

> BigInt('abc')
SyntaxError: Cannot convert abc to a BigInt

サフィックス 'n' は許可されていません。

> BigInt('123n')
SyntaxError: Cannot convert 123n to a BigInt

bigint リテラルのすべての基数は許可されています。

> BigInt('123')
123n
> BigInt('0xFF')
255n
> BigInt('0b1101')
13n
> BigInt('0o777')
511n
18.5.1.3 非整数数値は例外を発生させます
> BigInt(123.45)
RangeError: The number 123.45 cannot be converted to a BigInt because
it is not an integer
> BigInt(123)
123n
18.5.1.4 オブジェクトの変換

オブジェクトを bigint に変換する方法は構成可能です。たとえば、.valueOf() をオーバーライドすることで構成できます。

> BigInt({valueOf() {return 123n}})
123n

18.5.2 BigInt.prototype.* メソッド

BigInt.prototype には、プリミティブ bigint によって「継承」されるメソッドが含まれています。

18.5.3 BigInt.* メソッド

18.5.4 キャストと 64 ビット整数

キャストを使用すると、特定のビット数の整数値を作成できます。64 ビット整数だけに制限する場合は、常にキャストする必要があります。

const uint64a = BigInt.asUintN(64, 12345n);
const uint64b = BigInt.asUintN(64, 67890n);
const result = BigInt.asUintN(64, uint64a * uint64b);

18.6 bigint を他のプリミティブ型への強制変換

この表は、bigint を他のプリミティブ型に変換した場合に何が起こるかを示しています。

変換先 明示的な変換 強制変換(暗黙的な変換)
boolean Boolean(0n) false !0n true
Boolean(int) true !int false
number Number(7n) 7 (例) +int TypeError (1)
string String(7n) '7' (例) ''+7n '7' (例)

脚注

18.7 64 ビット値のための TypedArray と DataView の操作

bigint により、TypedArray と DataView は 64 ビット値をサポートできるようになりました。

18.8 bigint と JSON

JSON 標準は固定されており、変更されません。メリットは、古い JSON パーシングコードが時代遅れになることがないことです。デメリットは、JSON を拡張して bigint を含めることができないことです。

bigint を文字列化すると例外がスローされます。

> JSON.stringify(123n)
TypeError: Do not know how to serialize a BigInt
> JSON.stringify([123n])
TypeError: Do not know how to serialize a BigInt

18.8.1 bigint の文字列化

したがって、bigint を文字列として保存するのが最善策です。

const bigintPrefix = '[[bigint]]';

function bigintReplacer(_key, value) {
  if (typeof value === 'bigint') {
    return bigintPrefix + value;
  }
  return value;
}

const data = { value: 9007199254740993n };
assert.equal(
  JSON.stringify(data, bigintReplacer),
  '{"value":"[[bigint]]9007199254740993"}'
);

18.8.2 bigint のパース

次のコードは、前の例で生成したもののような文字列をパースする方法を示しています。

function bigintReviver(_key, value) {
  if (typeof value === 'string' && value.startsWith(bigintPrefix)) {
    return BigInt(value.slice(bigintPrefix.length));
  }
  return value;
}

const str = '{"value":"[[bigint]]9007199254740993"}';
assert.deepEqual(
  JSON.parse(str, bigintReviver),
  { value: 9007199254740993n }
);

18.9 FAQ: bigint

18.9.1 数値と bigint のどちらを使用するかをどのように決定しますか?

私の推奨事項

既存のすべての Web API は数値のみを返し、受け入れます。bigint にアップグレードされるのは、ケースバイケースです。

18.9.2 bigint と同じように数値の精度を向上させないのはなぜですか?

考えられるのは、numberintegerdouble に分割することですが、これにより言語に多くの新しい複雑さが追加されます(いくつかの整数専用の演算子など)。その影響については、Gistで概説しています。


謝辞