BigInt
Baseline
広く利用可能
この機能は広く実装されており、多くのバージョンの端末やブラウザーで動作します。2020年9月以降、すべてのブラウザーで利用可能です。
解説
BigInt 値は、単に BigInt と呼ばれることもありますが、長整数 (bigint) プリミティブです。整数リテラルの末尾に n を追加するか、BigInt() コンストラクターを呼び出し、整数値または文字列値を与えることで生成することができます(ただし new 演算子なしで)。
const previouslyMaxSafeInteger = 9007199254740991n;
const alsoHuge = BigInt(9007199254740991);
// 9007199254740991n
const hugeString = BigInt("9007199254740991");
// 9007199254740991n
const hugeHex = BigInt("0x1fffffffffffff");
// 9007199254740991n
const hugeOctal = BigInt("0o377777777777777777");
// 9007199254740991n
const hugeBin = BigInt(
"0b11111111111111111111111111111111111111111111111111111",
);
// 9007199254740991n
長整数はいくつかの点で数値と似ていますが、重要ないくつかの点が異なります。組み込みの Math オブジェクト内のメソッドでは利用できず、演算で数値と混ぜることができません。同じ型に統一する必要があります。ただし、長整数を数値へ変換する際には精度が落ちることがあるので、相互に変換する場合には注意が必要です。
型情報
typeof の長整数値 (bigint プリミティブ) に対する評価値は、"bigint" となります。
typeof 1n === "bigint"; // true
typeof BigInt("1") === "bigint"; // true
長整数値は Object でラップすることができます。
typeof Object(1n) === "object"; // true
演算子
以下の演算子は長整数値またはオブジェクトでラップした BigInt 値で使用することができます。
- 算術演算子:
+,-,*,/,%,** - ビット操作演算子:
>>,<<,&,|,^,~ - 単項マイナス (
-) - インクリメント/デクリメント:
++,--
論理値を返す演算子は、数値と長整数をオペランドとして混合できます。
長整数では全く対応していない演算子が 2 つあります。
- 単項プラス (
+) は、asm.js での使用法と競合するためサポートできず、asm.js を壊さないために除外されています。 - 符号なし右シフト (
>>>)は、ビット演算子で対応していない唯一のものです。すべての長整数が符号つきだからです。
特殊な場合もあります。
- 文字列と長整数の加算 (
+) は、文字列を返します。 - 除算 (
/) は小数部分を0方向に切り捨てます。長整数は小数部分を表現できないためです。
const previousMaxSafe = BigInt(Number.MAX_SAFE_INTEGER); // 9007199254740991n
const maxPlusOne = previousMaxSafe + 1n; // 9007199254740992n
const theFuture = previousMaxSafe + 2n; // 9007199254740993n、これが動作するようになりました。
const prod = previousMaxSafe * 2n; // 18014398509481982n
const diff = prod - 10n; // 18014398509481972n
const mod = prod % 10n; // 2n
const bigN = 2n ** 54n; // 18014398509481984n
bigN * -1n; // -18014398509481984n
const expected = 4n / 2n; // 2n
const truncated = 5n / 2n; // 2n であり、 2.5n ではない
比較演算
長整数値は数値と厳密等価ではありませんが、等価にはなります。
0n === 0; // false
0n == 0; // true
数値と長整数値は通常通り比較できます。
1n < 2; // true
2n > 1; // true
2 > 2; // false
2n > 2; // false
2n >= 2; // true
長整数値と数値は配列内に混在させて並べ替えることができます。
const mixed = [4n, 6, -12n, 10, 4, 0, 0n];
// [4n, 6, -12n, 10, 4, 0, 0n]
mixed.sort(); // 既定の並べ替えの動作
// [ -12n, 0, 0n, 10, 4n, 4, 6 ]
mixed.sort((a, b) => a - b);
// 型が混在した減算はできないので動作しない
// TypeError: can't convert BigInt value to Number value
// 適切な数値比較関数を使用した並べ替え
mixed.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
// [ -12n, 0, 0n, 4n, 4, 6, 10 ]
なお、Object にラップされた長整数は他のオブジェクトと同様の振る舞いをします。同じインスタンス同士が比較された場合にのみ等価となります。
Object(0n) === 0n; // false
Object(0n) === Object(0n); // false
const o = Object(0n);
o === o; // true
数値と長整数の型変換は精度が落ちる可能性があるため、次のことを推奨します。
- 長整数は、値が 253 を超えることが合理的に予想される場合にのみ使用する。
- 長整数と数値の間で型変換を行わない。
条件式
長整数は次のような場合は数値のように動作します。
if (0n) {
console.log("Hello from the if!");
} else {
console.log("Hello from the else!");
}
// "Hello from the else!"
0n || 12n; // 12n
0n && 12n; // 0n
Boolean(0n); // false
Boolean(12n); // true
!12n; // false
!0n; // true
暗号処理
長整数で対応している演算は、実行時間が一定ではないので、タイミング攻撃を受ける可能性があります。したがって、緩和要因がなければ JavaScript の長整数を暗号処理で使うのは危険な可能性があります。非常に一般的な例として、攻撃者は 101n ** 65537n と 17n ** 9999n の時間差を計測し、経過時間にもとづいて秘密鍵のような秘匿情報の大きさを推定することができます。もしそれでも長整数を使用しなければならない場合は、この問題に関する一般的なアドバイスが掲載されているタイミング攻撃のFAQをご覧ください。
JSON での使用
JSON.stringify() を長整数に対して使用すると TypeError が発生します。長整数は既定で JSON のシリアライズに対応していないためです。ただし、JSON.stringify() は特別に長整数に対する裏口を残しており、長整数の toJSON() メソッドを呼び出そうとします(他のプリミティブ値に対してはそうしません)。したがって、自身で toJSON() メソッドを実装することができます(組み込みにオブジェクトにパッチを当てることが明確に抑止されていない数少ない例の一つ)。
BigInt.prototype.toJSON = function () {
return { $bigint: this.toString() };
};
エラーを発生させるかわりに、JSON.stringify() は次のような文字列を生成します。
console.log(JSON.stringify({ a: 1n }));
// {"a":{"$bigint":"1"}}
もし BigInt.prototype にパッチを当てたくない場合は、長整数のシリアライズに JSON.stringify() の replacer 引数を使うことができます。
const replacer = (key, value) =>
typeof value === "bigint" ? { $bigint: value.toString() } : value;
const data = {
number: 1,
big: 18014398509481982n,
};
const stringified = JSON.stringify(data, replacer);
console.log(stringified);
// {"number":1,"big":{"$bigint":"18014398509481982"}}
長整数になることがわかっている値を含む JSON データがある場合は、JSON.parse の reviver 引数を使って対処することができます。
const reviver = (key, value) =>
value !== null &&
typeof value === "object" &&
"$bigint" in value &&
typeof value.$bigint === "string"
? BigInt(value.$bigint)
: value;
const payload = '{"number":1,"big":{"$bigint":"18014398509481982"}}';
const parsed = JSON.parse(payload, reviver);
console.log(parsed);
// { number: 1, big: 18014398509481982n }
メモ:
JSON.stringify() の replacer を汎用的にし、すべてのオブジェクトに対して長整数を適切にシリアライズをすることが可能であるのに対し、JSON.parse() の reviver は期待するデータ本体の形に特化していなければなりません。というのも、シリアライズは非可逆的だからです。つまり長整数を表現する文字列と通常の文字列を区別することはできません。
さらに、上記の例では置換と復元時にオブジェクト全体を生成するため、多数の長整数を含む大規模なオブジェクトではパフォーマンスやストレージに影響する可能性があります。データ本体の形が分かっている場合は、代わりに文字列としてシリアライズし、プロパティキー名に基づいて復元した方が良い場合があります。
実際、JSON では任意の長さの数値リテラルが許可されています。ただし、JavaScript では完全な精度で解析できないだけです。より長い整数(64 ビット整数など)に対応している言語で動作する別のプログラムと通信する場合、長整数を JSON 文字列ではなく JSON 数値として送信したい場合は、損失のない数値のシリアライズを参照してください。
長整数の変換
長整数を期待する多くの組み込み演算は、まず引数を長整数に変換します。演算は以下のように要約できます。
- 長整数はそのまま返される。
undefinedとnullはTypeErrorが発生する。trueは1nになり、falseは0nになる。- 文字列は整数リテラルを含むかのように解析され、変換される。解析に失敗すると
SyntaxErrorが発生する。この構文は文字列の数値リテラルのサブセットであり、小数点や指数記号は許可されません。 - 数値は
TypeErrorを発生させ、精度の低下を引き起こす意図しない暗黙の変換を防ぐ。 - シンボルは
TypeErrorを発生させる。 - オブジェクトはまず、
[Symbol.toPrimitive]()("number"をヒントに指定)、valueOf()、toString()の順にメソッドを呼び出してプリミティブ変換される。その結果のプリミティブは長整数に変換される。
JavaScript でほぼ同じ効果を得る最良の方法は、BigInt() 関数を使うことです。BigInt(x) は同じアルゴリズムを使って x を変換しますが、数値が TypeError を発生させず、整数であれば長整数に変換される点が異なります。
長整数を期待する組み込み演算は、変換後に長整数を固定幅に切り詰めることが多いことに注意してください。これは BigInt.asIntN() と BigInt.asUintN() 、および BigInt64Array と BigUint64Array のメソッドも含みます。
コンストラクター
BigInt()-
長整数型のプリミティブ値を返します。
newで呼び出された場合、エラーが発生します。
静的メソッド
BigInt.asIntN()-
長整数値を符号付き整数値に丸め、その値を返します。
BigInt.asUintN()-
長整数値を符号なし整数値に丸め、その値を返します。
インスタンスプロパティ
以下のプロパティは BigInt.prototype で定義されており、すべての BigInt インスタンスで共有されます。
BigInt.prototype.constructor-
インスタンスオブジェクトを作成したコンストラクター関数。
BigIntインスタンスの場合、初期値はBigIntコンストラクターです。 BigInt.prototype[Symbol.toStringTag]-
[Symbol.toStringTag]プロパティの初期値は文字列"BigInt"です。このプロパティはObject.prototype.toString()で使われます。ただし、BigIntも独自のtoString()メソッドをもつので、thisArgとして長整数を指定してObject.prototype.toString.call()を呼び出さないかぎりこのプロパティは使われません。
インスタンスメソッド
BigInt.prototype.toLocaleString()-
この長整数値の言語に合わせた表現の文字列を返します。
Object.prototype.toLocaleString()メソッドを上書きします。 BigInt.prototype.toString()-
この長整数値を指定された基数で表現した文字列を返します。
Object.prototype.toString()メソッドを上書きします。 BigInt.prototype.valueOf()-
この長整数値を返します。
Object.prototype.valueOf()メソッドを上書きします。
例
>素数の計算
function isPrime(n) {
if (n < 2n) {
return false;
}
if (n % 2n === 0n) {
return n === 2n;
}
for (let factor = 3n; factor * factor <= n; factor += 2n) {
if (n % factor === 0n) {
return false;
}
}
return true;
}
// 引数として長整数を取り、 nth 番目の素数を長整数として返します。
function nthPrime(nth) {
let maybePrime = 2n;
let prime = 0n;
while (nth >= 0n) {
if (isPrime(maybePrime)) {
nth--;
prime = maybePrime;
}
maybePrime++;
}
return prime;
}
nthPrime(20n);
// 73n
メモ:
このisPrime()の実装はあくまでデモンストレーション用です。実際のアプリケーションでは、繰り返し計算を避けるため、エラトステネスの篩のような高度なメモリーアルゴリズムを適用すべきです。
仕様書
| 仕様書 |
|---|
| ECMAScript® 2027 Language Specification> # sec-bigint-objects> |