==
)と不等価性(!=
)===
)と不等価性(!==
)この章では、必要に応じてストレージサイズが増減するJavaScriptの整数型であるBigIntについて見ていきます。
ECMAScript 2020以前、JavaScriptは整数を次のように扱っていました。
浮動小数点数と整数の型は1つだけで、64ビット浮動小数点数(IEEE 754倍精度)でした。
内部的には、ほとんどのJavaScriptエンジンは整数を透過的にサポートしていました。数値に小数点以下がなく、特定の範囲内にある場合、内部的には真の整数として格納できます。この表現は小さい整数と呼ばれ、通常は32ビットに収まります。たとえば、64ビット版V8エンジンの小さい整数の範囲は−231から231−1です。(出典)
JavaScriptの数値は、小さい整数の範囲を超える整数も、浮動小数点数として表現できます。ここで、安全な範囲は±53ビットです。このトピックの詳細については、§16.9.3「安全な整数」を参照してください。
場合によっては、符号付き53ビットを超える必要があることがあります。例えば
BigIntは、整数のための新しいプリミティブデータ型です。BigIntはビット数に固定されたストレージサイズを持たず、表現する整数に合わせてサイズが調整されます。
BigIntリテラルは、1つ以上の数字のシーケンスで、`n`で終わります。例えば
123n
`-`や`*`などの演算子はオーバーロードされており、BigIntでも動作します。
> 123n * 456n56088n
BigIntはプリミティブ値です。`typeof`はそれらに対して新しい結果を返します。
> typeof 123n'bigint'
JavaScriptの数値は内部的には、分数に指数を掛けたものとして表現されます(詳細については、§16.8「背景:浮動小数点精度」を参照)。その結果、最大の安全な整数 253−1を超えると、表現できる整数はまだありますが、それらの間には隙間があります。
> 2**53 - 2 // safe9007199254740990
> 2**53 - 1 // safe9007199254740991
> 2**53 // unsafe, same as next integer9007199254740992
> 2**53 + 19007199254740992
> 2**53 + 29007199254740994
> 2**53 + 39007199254740996
> 2**53 + 49007199254740996
> 2**53 + 59007199254740996
BigIntを使用すると、53ビットを超えることができます。
> 2n**53n9007199254740992n
> 2n**53n + 1n9007199254740993n
> 2n**53n + 2n9007199254740994n
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;
}
}
}
.deepEqual(
assert1n, 2n, 3n, 4n, 5n].map(nth => nthPrime(nth)),
[2n, 3n, 5n, 7n, 11n]
[; )
数値リテラルと同様に、BigIntリテラルはいくつかの基数をサポートしています。
負のBigIntは、単項マイナス演算子を前に付けることで生成されます:`-0123n`
_
)によるセパレータ [ES2021]数値リテラルと同様に、BigIntリテラルでも下線(_
)をセパレータとして使用できます。
const massOfEarthInKg = 6_000_000_000_000_000_000_000_000n;
BigIntは、金融技術分野で金額を表すために頻繁に使用されます。ここでセパレータも役立ちます。
const priceInCents = 123_000_00n; // 123 thousand dollars
数値リテラルと同様に、2つの制限が適用されます。
ほとんどの演算子では、BigIntと数値を混在させることは許可されていません。混在させると、例外がスローされます。
> 2n + 1TypeError: Cannot mix BigInt and other types, use explicit conversions
この規則の理由は、数値とBigIntを共通の型に強制変換する一般的な方法がないためです。数値は53ビットを超えるBigIntを表すことができず、BigIntは分数を表すことができません。したがって、例外は予期しない結果につながる可能性のあるタイプミスについて警告します。
例えば、次の式の結果は`9007199254740993n`ですか、それとも`9007199254740992`ですか?
2**53 + 1n
次の式の結果も不明です。
2n**53n * 3.3
2項`+`、2項`-`、`*`、`**`は期待通りに動作します。
> 7n * 3n21n
BigIntと文字列を混在させることは問題ありません。
> 6n + ' apples''6 apples'
`/`、`%`はゼロに向かって丸めます(`Math.trunc()`と同様)。
> 1n / 2n0n
単項`-`は期待通りに動作します。
> -(-64n)64n
多くのコードが被演算子を数値に変換するために単項`+`に依存しているため、BigIntではサポートされていません。
> +23nTypeError: Cannot convert a BigInt value to a number
比較演算子`<`、`>`、`>=`、`<=`は期待通りに動作します。
> 17n <= 17ntrue
> 3n > -1ntrue
BigIntと数値の比較にはリスクがないため、BigIntと数値を混在させることができます。
> 3n > -1true
ビット演算子は、数値を32ビット整数として解釈します。これらの整数は符号なしまたは符号付きです。符号付きの場合、整数の負数はその2の補数です(オーバーフローを無視して整数に2の補数を追加すると、0になります)。
> 2**32-1 >> 0-1
これらの整数は固定サイズであるため、最上位ビットは符号を示します。
> 2**31 >> 0 // highest bit is 1-2147483648
> 2**31 - 1 >> 0 // highest bit is 02147483647
BigIntの場合、ビット演算子は負の符号を無限の2の補数として解釈します。例えば
つまり、負の符号は、実際のビットとして表現されるのではなく、外部フラグのようなものです。
ビット反転(`~`)はすべてのビットを反転します。
> ~0b10n-3n
> ~0n-1n
> ~-2n1n
BigIntに2項ビット演算子を適用する方法は、数値に適用する場合と同様です。
> (0b1010n | 0b0111n).toString(2)'1111'
> (0b1010n & 0b0111n).toString(2)'10'
> (0b1010n | -1n).toString(2)'-1'
> (0b1010n & -1n).toString(2)'1010'
BigIntの符号付きシフト演算子は、数値の符号を保持します。
> 2n << 1n4n
> -2n << 1n-4n
> 2n >> 1n1n
> -2n >> 1n-1n
`-1n`は左に無限に伸びる1のシーケンスであることを思い出してください。そのため、左にシフトしても変わりません。
> -1n >> 20n-1n
BigIntには符号なし右シフト演算子はありません。
> 2n >>> 1nTypeError: BigInts have no unsigned right shift, use >> instead
なぜですか? 符号なし右シフトの考え方は、0が「左から」シフトされることです。つまり、有限の数の2進数が存在するという仮定に基づいています。
しかし、BigIntには「左」がありません。その2進数は無限に伸びています。これは負の数で特に重要です。
符号付き右シフトは、無限の桁数でも機能します。なぜなら、最上位桁が保持されるからです。したがって、BigIntに適合させることができます。
==
)と不等価性(!=
)ゆるい等価性(==
)と不等価性(!=
)は値を強制変換します。
> 0n == falsetrue
> 1n == truetrue
> 123n == 123true
> 123n == '123'true
===
)と不等価性(!==
)厳密な等価性(===
)と不等価性(!==
)は、型が同じ場合にのみ値を等しいとみなします。
> 123n === 123false
> 123n === 123ntrue
数値と同様に、BigIntには関連付けられたラッパーコンストラクタ`BigInt`があります。
`new BigInt()`:`TypeError`をスローします。
`BigInt(x)`は、任意の値`x`をBigIntに変換します。これは`Number()`と同様に動作しますが、いくつかの違いがあります。表13に要約し、以降の節で詳しく説明します。
x |
BigInt(x) |
---|---|
undefined |
`TypeError`をスロー |
null |
`TypeError`をスロー |
boolean | `false` → `0n`、`true` → `1n` |
number | 例:`123` → `123n` |
非整数 → `RangeError`をスロー | |
bigint | x(変更なし) |
string | 例:`'123'` → `123n` |
構文解析できない場合 → SyntaxError をスローします |
|
シンボル | `TypeError`をスロー |
オブジェクト | 構成可能 (例: .valueOf() を介して) |
undefined
と null
の変換x
が undefined
または null
のいずれかの場合、TypeError
がスローされます。
> BigInt(undefined)TypeError: Cannot convert undefined to a BigInt
> BigInt(null)TypeError: Cannot convert null to a BigInt
文字列が整数を表していない場合、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
> BigInt(123.45)RangeError: The number 123.45 cannot be converted to a BigInt because
it is not an integer
> BigInt(123)123n
オブジェクトを bigint に変換する方法は構成可能です。たとえば、.valueOf()
をオーバーライドすることで構成できます。
> BigInt({valueOf() {return 123n}})123n
BigInt.prototype.*
メソッドBigInt.prototype
には、プリミティブ bigint によって「継承」されるメソッドが含まれています。
BigInt.prototype.toLocaleString(locales?, options?)
BigInt.prototype.toString(radix?)
BigInt.prototype.valueOf()
BigInt.*
メソッドBigInt.asIntN(width, theInt)
theInt
を width
ビット(符号付き)にキャストします。これは、値が内部的にどのように表現されるかに影響します。
BigInt.asUintN(width, theInt)
theInt
を width
ビット(符号なし)にキャストします。
キャストを使用すると、特定のビット数の整数値を作成できます。64 ビット整数だけに制限する場合は、常にキャストする必要があります。
const uint64a = BigInt.asUintN(64, 12345n);
const uint64b = BigInt.asUintN(64, 67890n);
const result = BigInt.asUintN(64, uint64a * uint64b);
この表は、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' (例) |
脚注
+
は bigint ではサポートされていません。多くのコードが、それを用いてオペランドを数値に変換することに依存しているためです。bigint により、TypedArray と DataView は 64 ビット値をサポートできるようになりました。
BigInt64Array
BigUint64Array
DataView.prototype.getBigInt64()
DataView.prototype.setBigInt64()
DataView.prototype.getBigUint64()
DataView.prototype.setBigUint64()
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
したがって、bigint を文字列として保存するのが最善策です。
const bigintPrefix = '[[bigint]]';
function bigintReplacer(_key, value) {
if (typeof value === 'bigint') {
return bigintPrefix + value;
}return value;
}
const data = { value: 9007199254740993n };
.equal(
assertJSON.stringify(data, bigintReplacer),
'{"value":"[[bigint]]9007199254740993"}'
; )
次のコードは、前の例で生成したもののような文字列をパースする方法を示しています。
function bigintReviver(_key, value) {
if (typeof value === 'string' && value.startsWith(bigintPrefix)) {
return BigInt(value.slice(bigintPrefix.length));
}return value;
}
const str = '{"value":"[[bigint]]9007199254740993"}';
.deepEqual(
assertJSON.parse(str, bigintReviver),
value: 9007199254740993n }
{ ; )
私の推奨事項
Array.prototype.forEach()
Array.prototype.entries()
既存のすべての Web API は数値のみを返し、受け入れます。bigint にアップグレードされるのは、ケースバイケースです。
考えられるのは、number
を integer
と double
に分割することですが、これにより言語に多くの新しい複雑さが追加されます(いくつかの整数専用の演算子など)。その影響については、Gistで概説しています。
謝辞