BigInt
は、JavaScript の新しい数値プリミティブであり、任意精度で整数を表すことができます。 BigInt
を使用すると、Number
の安全な整数制限を超えても、大きな整数を安全に格納および操作できます。この記事では、いくつかのユースケースを説明し、JavaScript の Number
と BigInt
を比較することで、Chrome 67 の新機能を解説します。
ユースケース #
任意精度整数は、JavaScript に多くの新しいユースケースをもたらします。
BigInt
を使用すると、オーバーフローすることなく、整数演算を正確に実行できます。それ自体で、数え切れないほどの新しい可能性が広がります。たとえば、金融テクノロジーでは、大きな数値に対する数学的な演算が一般的に使用されます。
大きな整数ID や 高精度のタイムスタンプ は、JavaScript では Number
として安全に表現できません。これにより、しばしば 実際のバグ につながり、JavaScript開発者は代わりに文字列として表現せざるを得ませんでした。 BigInt
を使用すると、このデータを数値として表現できるようになりました。
BigInt
は、最終的な BigDecimal
実装の基礎を形成する可能性があります。これは、10進数の精度で金額を表したり、それらを正確に操作したりするのに役立ちます(いわゆる 0.10 + 0.20 !== 0.30
の問題)。
これまで、これらのユースケースを持つJavaScriptアプリケーションは、BigInt
のような機能をエミュレートするユーザランドライブラリに頼らざるを得ませんでした。 BigInt
が広く利用可能になると、そのようなアプリケーションは、ネイティブの BigInt
を優先して、これらのランタイム依存関係を削除できます。これにより、ロード時間、解析時間、コンパイル時間が短縮され、さらにランタイムパフォーマンスが大幅に向上します。
BigInt
実装は、一般的なユーザランドライブラリよりも優れたパフォーマンスを発揮します。現状: Number
#
JavaScript の Number
は、倍精度浮動小数点数として表現されます。これは、精度が制限されていることを意味します。 Number.MAX_SAFE_INTEGER
定数は、安全にインクリメントできる最大の整数を示します。その値は 2**53-1
です。
const max = Number.MAX_SAFE_INTEGER;
// → 9_007_199_254_740_991
注: 読みやすくするために、この大きな数の桁を、区切り文字としてアンダースコアを使用して、千単位でグループ化しています。数値リテラル区切り文字の提案は、一般的なJavaScriptの数値リテラルでまさにそれを実現します。
一度インクリメントすると、期待どおりの結果が得られます。
max + 1;
// → 9_007_199_254_740_992 ✅
ただし、2 回目にインクリメントすると、結果は JavaScript の Number
として正確に表現できなくなります。
max + 2;
// → 9_007_199_254_740_992 ❌
max + 1
が max + 2
と同じ結果を生成することに注意してください。JavaScript でこの特定の値を取得したときは、それが正確かどうかを判断する方法はありません。安全な整数範囲(つまり、Number.MIN_SAFE_INTEGER
から Number.MAX_SAFE_INTEGER
まで)外の整数に対する計算は、精度が失われる可能性があります。このため、安全な範囲内の数値整数値のみを信頼できます。
新しいホットな機能: BigInt
#
BigInt
は、JavaScript の新しい数値プリミティブであり、任意精度で整数を表すことができます。 BigInt
を使用すると、Number
の安全な整数制限を超えても、大きな整数を安全に格納および操作できます。
BigInt
を作成するには、任意の整数リテラルに n
サフィックスを追加します。たとえば、123
は 123n
になります。グローバルな BigInt(number)
関数を使用して、Number
を BigInt
に変換できます。言い換えれば、BigInt(123) === 123n
です。これらの 2 つの手法を使用して、以前に抱えていた問題を解決してみましょう。
BigInt(Number.MAX_SAFE_INTEGER) + 2n;
// → 9_007_199_254_740_993n ✅
次に別の例を示します。ここでは、2 つの Number
を乗算しています。
1234567890123456789 * 123;
// → 151851850485185200000 ❌
最下位桁の 9
と 3
を見ると、乗算の結果は 7
で終わるはずであることがわかります(9 * 3 === 27
のため)。ただし、結果は一連のゼロで終わっています。それは正しいはずがありません! 代わりに BigInt
でもう一度試してみましょう。
1234567890123456789n * 123n;
// → 151851850485185185047n ✅
今回は正しい結果が得られます。
Number
の安全な整数制限は BigInt
には適用されません。したがって、BigInt
を使用すると、精度が失われることを心配することなく、正確な整数演算を実行できます。
新しいプリミティブ #
BigInt
は、JavaScript 言語の新しいプリミティブです。そのため、typeof
演算子を使用して検出できる独自の型を取得します。
typeof 123;
// → 'number'
typeof 123n;
// → 'bigint'
BigInt
は別の型であるため、BigInt
は Number
と厳密に等しくなることはありません。たとえば、42n !== 42
です。 BigInt
を Number
と比較するには、比較を行う前に、それらのいずれかを他方の型に変換するか、抽象的な等価性(==
)を使用します。
42n === BigInt(42);
// → true
42n == 42;
// → true
ブール値に強制変換される場合(たとえば、if
、&&
、||
、または Boolean(int)
を使用する場合)、BigInt
は Number
と同じロジックに従います。
if (0n) {
console.log('if');
} else {
console.log('else');
}
// → logs 'else', because `0n` is falsy.
演算子 #
BigInt
は、最も一般的な演算子をサポートします。バイナリの +
、-
、*
、および **
は、すべて期待どおりに機能します。 /
と %
は機能し、必要に応じてゼロに向かって丸めます。ビット演算 |
、&
、<<
、>>
、および ^
は、負の値に対して 2 の補数表現 を仮定してビット単位の演算を実行します。これは、Number
の場合と同じです。
(7 + 6 - 5) * 4 ** 3 / 2 % 3;
// → 1
(7n + 6n - 5n) * 4n ** 3n / 2n % 3n;
// → 1n
単項 -
を使用して、負の BigInt
値を示すことができます。たとえば、-42n
です。 単項 +
は、+x
が常に Number
または例外を生成することを期待する asm.js コードを壊すため、サポートされていません。
注意すべき点の 1 つは、BigInt
と Number
の間で演算を混在させることが許可されていないことです。これは、暗黙的な強制変換によって情報が失われる可能性があるため、良いことです。次の例を考えてみましょう。
BigInt(Number.MAX_SAFE_INTEGER) + 2.5;
// → ?? 🤔
結果はどうなるべきでしょうか? ここに良い答えはありません。 BigInt
は分数を表現できず、Number
は安全な整数制限を超える BigInt
を表現できません。そのため、BigInt
と Number
の間で演算を混在させると、TypeError
例外が発生します。
このルールの唯一の例外は、===
(前述)などの比較演算子、<
、および >=
です。これらはブール値を返すため、精度の損失のリスクはありません。
1 + 1n;
// → TypeError
123 < 124n;
// → true
BigInt
と Number
は一般的に混在しないため、既存のコードをオーバーロードしたり、Number
の代わりに BigInt
を使用するように魔法のように「アップグレード」したりしないでください。操作する 2 つのドメインのどちらかを決定し、それに固執してください。潜在的に大きな整数を操作する新しいAPIの場合、BigInt
が最適な選択肢です。 Number
は、安全な整数範囲内にあることがわかっている整数値に対しては依然として理にかなっています。
もう 1 つ注意すべき点は、符号なし右シフトを実行する>>>
演算子は、常に符号付きであるため、BigInt
では意味がないということです。このため、>>>
は BigInt
では機能しません。
API #
いくつかの新しい BigInt
固有の API が利用可能です。
グローバルな BigInt
コンストラクターは、Number
コンストラクターに似ています。(前述のとおり) 引数を BigInt
に変換します。変換が失敗すると、SyntaxError
または RangeError
例外がスローされます。
BigInt(123);
// → 123n
BigInt(1.5);
// → RangeError
BigInt('1.5');
// → SyntaxError
これらの最初の例では、数値リテラルを BigInt()
に渡しています。これは、Number
が精度損失の影響を受けるため、BigInt
変換が発生する前に精度が失われる可能性があるため、悪い習慣です。
BigInt(123456789123456789);
// → 123456789123456784n ❌
このため、BigInt
リテラル表記(n
サフィックス付き)、または文字列(Number
ではない!)を BigInt()
に渡すことをお勧めします。
123456789123456789n;
// → 123456789123456789n ✅
BigInt('123456789123456789');
// → 123456789123456789n ✅
2 つのライブラリ関数を使用すると、BigInt
値を符号付きまたは符号なしの整数としてラップでき、特定のビット数に制限されます。 BigInt.asIntN(width, value)
は、BigInt
値を width
桁の 2 進数符号付き整数にラップし、BigInt.asUintN(width, value)
は、BigInt
値を width
桁の 2 進数符号なし整数にラップします。たとえば、64 ビット演算を実行している場合は、これらの API を使用して適切な範囲内に収めることができます。
// Highest possible BigInt value that can be represented as a
// signed 64-bit integer.
const max = 2n ** (64n - 1n) - 1n;
BigInt.asIntN(64, max);
→ 9223372036854775807n
BigInt.asIntN(64, max + 1n);
// → -9223372036854775808n
// ^ negative because of overflow
64 ビット整数範囲(つまり、絶対数値の 63 ビット + 符号用の 1 ビット)を超える BigInt
値を渡すとすぐにオーバーフローが発生することに注意してください。
BigInt
を使用すると、他のプログラミング言語で一般的に使用される 64 ビット符号付き整数と符号なし整数を正確に表現できます。 2 つの新しい型付き配列フレーバーである BigInt64Array
と BigUint64Array
を使用すると、このような値のリストを効率的に表現および操作することが容易になります。
const view = new BigInt64Array(4);
// → [0n, 0n, 0n, 0n]
view.length;
// → 4
view[0];
// → 0n
view[0] = 42n;
view[0];
// → 42n
BigInt64Array
フレーバーは、その値が符号付き 64 ビット制限内に収まるようにします。
// Highest possible BigInt value that can be represented as a
// signed 64-bit integer.
const max = 2n ** (64n - 1n) - 1n;
view[0] = max;
view[0];
// → 9_223_372_036_854_775_807n
view[0] = max + 1n;
view[0];
// → -9_223_372_036_854_775_808n
// ^ negative because of overflow
BigUint64Array
フレーバーは、代わりに符号なし 64 ビット制限を使用して同じことを行います。
BigInt のポリフィルとトランスパイル #
執筆時点では、BigInt
は Chrome でのみサポートされています。他のブラウザは、それらを実装するために積極的に取り組んでいます。しかし、ブラウザの互換性を犠牲にすることなく、今日 BigInt
機能を使用したい場合はどうすればよいでしょうか? 聞いてくれて嬉しいです!答えは…控えめに言って興味深いものです。
他のほとんどの最新の JavaScript 機能とは異なり、BigInt
は ES5 に合理的にトランスパイルできません。
BigInt
の提案は、BigInt
で動作するように(+
、>=
などの)演算子の動作を変更します。これらの変更は直接ポリフィルすることが不可能であり、また、Babel または同様のツールを使用して BigInt
コードをフォールバックコードにトランスパイルすることを(ほとんどの場合)不可能にしています。その理由は、このようなトランスパイルでは、プログラム内のすべての単一の演算子を、入力で型チェックを実行する関数の呼び出しに置き換える必要があり、容認できないランタイムパフォーマンスのペナルティが発生するためです。さらに、トランスパイルされたバンドルのファイルサイズが大幅に増加し、ダウンロード、解析、コンパイル時間に悪影響を及ぼします。
より実現可能で将来性のある解決策は、当面の間、JSBIライブラリを使用してコードを記述することです。 JSBI は、V8 および Chrome の BigInt
実装の JavaScript ポートです。設計上、ネイティブの BigInt
機能とまったく同じように動作します。違いは、構文に依存するのではなく、APIを公開することです。
import JSBI from './jsbi.mjs';
const max = JSBI.BigInt(Number.MAX_SAFE_INTEGER);
const two = JSBI.BigInt('2');
const result = JSBI.add(max, two);
console.log(result.toString());
// → '9007199254740993'
すべてのブラウザーで BigInt
がネイティブにサポートされるようになったら、babel-plugin-transform-jsbi-to-bigint
を使用してコードをネイティブの BigInt
コードにトランスパイルし、JSBI 依存関係を削除できます。たとえば、上記の例は以下のようにトランスパイルされます。
const max = BigInt(Number.MAX_SAFE_INTEGER);
const two = 2n;
const result = max + two;
console.log(result);
// → '9007199254740993'
さらに詳しい情報 #
もしあなたがBigInt
が舞台裏でどのように動作するのか(例えば、メモリ内でどのように表現され、それらに対する演算がどのように実行されるのか)に興味があるなら、実装の詳細を解説したV8のブログ記事をご覧ください。
BigInt
のサポート #
- Chrome: バージョン67以降でサポート
- Firefox: バージョン68以降でサポート
- Safari: バージョン14以降でサポート
- Node.js: バージョン12以降でサポート
- Babel: サポート