==) und Ungleichheit (!=)===) und Ungleichheit (!==)BigIntBigInt als Konstruktor und als FunktionBigInt.prototype.*BigInt.*In diesem Kapitel befassen wir uns mit Bigints, den Ganzzahlen in JavaScript, deren Speicherplatz nach Bedarf wächst und schrumpft.
Vor ECMAScript 2020 behandelte JavaScript Ganzzahlen wie folgt:
Es gab nur einen einzigen Typ für Gleitkommazahlen und Ganzzahlen: 64-Bit-Gleitkommazahlen (IEEE 754 Double Precision).
Intern unterstützten die meisten JavaScript-Engines Ganzzahlen transparent: Wenn eine Zahl keine Dezimalstellen hat und in einem bestimmten Bereich liegt, kann sie intern als echte Ganzzahl gespeichert werden. Diese Darstellung wird als Small Integer bezeichnet und passt normalerweise in 32 Bits. Beispielsweise reicht der Bereich der Small Integers in der 64-Bit-Version der V8-Engine von −231 bis 231−1 (Quelle).
JavaScript-Zahlen konnten auch Ganzzahlen außerhalb des Small-Integer-Bereichs als Gleitkommazahlen darstellen. Hier beträgt der sichere Bereich plus/minus 53 Bits. Weitere Informationen zu diesem Thema finden Sie in §16.9.3 „Sichere Ganzzahlen“.
Manchmal benötigen wir mehr als 53 vorzeichenbehaftete Bits – zum Beispiel:
Bigint ist ein neuer primitiver Datentyp für Ganzzahlen. Bigints haben keine feste Speichergröße in Bits; ihre Größe passt sich an die dargestellten Ganzzahlen an.
Ein Bigint-Literal ist eine Abfolge von einer oder mehreren Ziffern, gefolgt von einem n – zum Beispiel:
123nOperatoren wie - und * sind überladen und funktionieren mit Bigints.
> 123n * 456n
56088nBigints sind primitive Werte. typeof gibt für sie ein neues Ergebnis zurück:
> typeof 123n
'bigint'JavaScript-Zahlen werden intern als Bruch multipliziert mit einem Exponenten dargestellt (siehe §16.8 „Hintergrund: Gleitkomma-Genauigkeit“ für Details). Infolgedessen gibt es, wenn wir über die höchste sichere Ganzzahl 253−1 hinausgehen, immer noch einige Ganzzahlen, die dargestellt werden können, aber mit Lücken dazwischen.
> 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
9007199254740996Bigints ermöglichen es uns, über 53 Bits hinauszugehen.
> 2n**53n
9007199254740992n
> 2n**53n + 1n
9007199254740993n
> 2n**53n + 2n
9007199254740994nSo sieht die Verwendung von Bigints aus (Code basierend auf einem Beispiel im Vorschlag):
/**
* 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]
);Ähnlich wie Zahlenliterale unterstützen Bigint-Literale mehrere Basen:
123n0xFFn0b1101n0o777nNegative Bigints werden durch Voranstellen des unären Minuszeichens erzeugt: -0123n
_) als Trennzeichen in Bigint-Literalen [ES2021]Genau wie bei Zahlenliteralen können wir Unterstriche (_) als Trennzeichen in Bigint-Literalen verwenden.
const massOfEarthInKg = 6_000_000_000_000_000_000_000_000n;Bigints werden oft zur Darstellung von Geld im Finanzsektor verwendet. Trennzeichen können auch hier helfen:
const priceInCents = 123_000_00n; // 123 thousand dollarsWie bei Zahlenliteralen gelten zwei Einschränkungen:
Bei den meisten Operatoren dürfen wir Bigints und Zahlen nicht mischen. Wenn wir es doch tun, werden Ausnahmen ausgelöst:
> 2n + 1
TypeError: Cannot mix BigInt and other types, use explicit conversionsDer Grund für diese Regel ist, dass es keine allgemeine Möglichkeit gibt, eine Zahl und ein Bigint in einen gemeinsamen Typ zu konvertieren: Zahlen können keine Bigints über 53 Bits hinaus darstellen, Bigints können keine Brüche darstellen. Daher warnen die Ausnahmen vor Tippfehlern, die zu unerwarteten Ergebnissen führen können.
Sollte das Ergebnis des folgenden Ausdrucks beispielsweise 9007199254740993n oder 9007199254740992 sein?
2**53 + 1nAuch ist nicht klar, was das Ergebnis des folgenden Ausdrucks sein sollte:
2n**53n * 3.3Binäres +, binäres -, *, ** funktionieren wie erwartet.
> 7n * 3n
21nDas Mischen von Bigints und Strings ist in Ordnung.
> 6n + ' apples'
'6 apples'/, % runden in Richtung Null (wie Math.trunc()).
> 1n / 2n
0nDas unäre - funktioniert wie erwartet.
> -(-64n)
64nDas unäre + wird für Bigints nicht unterstützt, da viel Code darauf angewiesen ist, dass es seinen Operanden in eine Zahl konvertiert.
> +23n
TypeError: Cannot convert a BigInt value to a numberVergleichsoperatoren <, >, >=, <= funktionieren wie erwartet.
> 17n <= 17n
true
> 3n > -1n
trueDas Vergleichen von Bigints und Zahlen birgt keine Risiken. Daher können wir Bigints und Zahlen mischen.
> 3n > -1
trueBitweise Operatoren interpretieren Zahlen als 32-Bit-Ganzzahlen. Diese Ganzzahlen sind entweder vorzeichenlos oder vorzeichenbehaftet. Wenn sie vorzeichenbehaftet sind, ist die Negation einer Ganzzahl ihr Zweierkomplement (das Addieren einer Ganzzahl zu ihrem Zweierkomplement – unter Ignorieren von Überläufen – ergibt Null).
> 2**32-1 >> 0
-1Aufgrund dieser festen Größe der Ganzzahlen geben ihre höchsten Bits ihr Vorzeichen an.
> 2**31 >> 0 // highest bit is 1
-2147483648
> 2**31 - 1 >> 0 // highest bit is 0
2147483647Für Bigints interpretieren bitweise Operatoren ein negatives Vorzeichen als unendliches Zweierkomplement – zum Beispiel:
-1 ist ···111111 (Einsen reichen unendlich nach links).-2 ist ···111110-3 ist ···111101-4 ist ···111100Das heißt, ein negatives Vorzeichen ist eher ein externes Flag und nicht als tatsächliches Bit repräsentiert.
~)Bitweise Nicht (~) invertiert alle Bits.
> ~0b10n
-3n
> ~0n
-1n
> ~-2n
1n&, |, ^)Die Anwendung binärer bitweiser Operatoren auf Bigints funktioniert analog zur Anwendung auf Zahlen.
> (0b1010n | 0b0111n).toString(2)
'1111'
> (0b1010n & 0b0111n).toString(2)
'10'
> (0b1010n | -1n).toString(2)
'-1'
> (0b1010n & -1n).toString(2)
'1010'<< und >>)Die vorzeichenbehafteten Shift-Operatoren für Bigints erhalten das Vorzeichen einer Zahl.
> 2n << 1n
4n
> -2n << 1n
-4n
> 2n >> 1n
1n
> -2n >> 1n
-1nErinnern Sie sich, dass -1n eine unendlich nach links reichende Sequenz von Einsen ist. Deshalb ändert sich beim Linksshift nichts.
> -1n >> 20n
-1n>>>)Es gibt keinen vorzeichenlosen Rechtsshift-Operator für Bigints.
> 2n >>> 1n
TypeError: BigInts have no unsigned right shift, use >> insteadWarum? Die Idee hinter dem vorzeichenlosen Rechtsshift ist, dass von „links“ eine Null hineingeschoben wird. Anders ausgedrückt, es wird angenommen, dass es eine endliche Anzahl von Binärziffern gibt.
Bei Bigints gibt es jedoch kein „links“, ihre Binärziffern erstrecken sich unendlich. Dies ist besonders bei negativen Zahlen wichtig.
Der vorzeichenbehaftete Rechtsshift funktioniert auch bei einer unendlichen Anzahl von Ziffern, da die höchste Ziffer erhalten bleibt. Daher kann er an Bigints angepasst werden.
==) und Ungleichheit (!=)Ungenauer Vergleich (==) und Ungleichheit (!=) konvertieren Werte.
> 0n == false
true
> 1n == true
true
> 123n == 123
true
> 123n == '123'
true===) und Ungleichheit (!==)Genauer Vergleich (===) und Ungleichheit (!==) betrachten Werte nur dann als gleich, wenn sie denselben Typ haben.
> 123n === 123
false
> 123n === 123n
trueBigIntAnalog zu Zahlen gibt es für Bigints den zugehörigen Wrapper-Konstruktor BigInt.
BigInt als Konstruktor und als Funktionnew BigInt(): löst einen TypeError aus.
BigInt(x) konvertiert beliebige Werte x in ein Bigint. Dies funktioniert ähnlich wie Number(), mit einigen Unterschieden, die in Tab. 13 zusammengefasst und in den folgenden Unterabschnitten detaillierter erklärt werden.
x |
BigInt(x) |
|---|---|
undefined |
Löst TypeError aus |
null |
Löst TypeError aus |
| boolean | false → 0n, true → 1n |
| number | Beispiel: 123 → 123n |
Nicht-Ganzzahl → löst RangeError aus |
|
| bigint | x (keine Änderung) |
| string | Beispiel: '123' → 123n |
Nicht parsenbar → löst SyntaxError aus |
|
| symbol | Löst TypeError aus |
| object | Konfigurierbar (z.B. über .valueOf()) |
undefined und nullEin TypeError wird ausgelöst, wenn x entweder undefined oder null ist.
> BigInt(undefined)
TypeError: Cannot convert undefined to a BigInt
> BigInt(null)
TypeError: Cannot convert null to a BigIntWenn ein String keine Ganzzahl darstellt, löst BigInt() einen SyntaxError aus (während Number() den Fehlerwert NaN zurückgibt).
> BigInt('abc')
SyntaxError: Cannot convert abc to a BigIntDas Suffix 'n' ist nicht erlaubt.
> BigInt('123n')
SyntaxError: Cannot convert 123n to a BigIntAlle Basen von Bigint-Literalen sind erlaubt.
> 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)
123nWie Objekte in Bigints konvertiert werden, kann konfiguriert werden – zum Beispiel durch Überschreiben von .valueOf().
> BigInt({valueOf() {return 123n}})
123nBigInt.prototype.*BigInt.prototype enthält die Methoden, die von primitiven Bigints „geerbt“ werden.
BigInt.prototype.toLocaleString(locales?, options?)BigInt.prototype.toString(radix?)BigInt.prototype.valueOf()BigInt.*BigInt.asIntN(width, theInt)
Kürzt theInt auf width Bits (vorzeichenbehaftet). Dies beeinflusst, wie der Wert intern dargestellt wird.
BigInt.asUintN(width, theInt)
Kürzt theInt auf width Bits (vorzeichenlos).
Casting ermöglicht es uns, Ganzzahlwerte mit einer bestimmten Anzahl von Bits zu erstellen. Wenn wir uns auf nur 64-Bit-Ganzzahlen beschränken wollen, müssen wir immer casten.
const uint64a = BigInt.asUintN(64, 12345n);
const uint64b = BigInt.asUintN(64, 67890n);
const result = BigInt.asUintN(64, uint64a * uint64b);Diese Tabelle zeigt, was passiert, wenn wir Bigints in andere primitive Typen konvertieren.
| Konvertierung nach | Explizite Konvertierung | Implizite Konvertierung |
|---|---|---|
| boolean | Boolean(0n) → false |
!0n → true |
Boolean(int) → true |
!int → false |
|
| number | Number(7n) → 7 (Beispiel) |
+int → TypeError (1) |
| string | String(7n) → '7' (Beispiel) |
''+7n → '7' (Beispiel) |
Fußnote
+ wird für Bigints nicht unterstützt, da viel Code darauf angewiesen ist, dass es seinen Operanden in eine Zahl konvertiert.Dank Bigints können Typed Arrays und DataViews 64-Bit-Werte unterstützen.
BigInt64ArrayBigUint64ArrayDataView.prototype.getBigInt64()DataView.prototype.setBigInt64()DataView.prototype.getBigUint64()DataView.prototype.setBigUint64()Der JSON-Standard ist fixiert und wird sich nicht ändern. Der Vorteil ist, dass alter JSON-Parsing-Code niemals veraltet sein wird. Der Nachteil ist, dass JSON nicht erweitert werden kann, um Bigints zu enthalten.
Das Stringifizieren von Bigints löst Ausnahmen aus.
> JSON.stringify(123n)
TypeError: Do not know how to serialize a BigInt
> JSON.stringify([123n])
TypeError: Do not know how to serialize a BigIntDaher ist unsere beste Option, Bigints in Strings zu speichern.
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"}'
);Der folgende Code zeigt, wie Strings wie derjenige, den wir im vorherigen Beispiel erzeugt haben, geparst werden.
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 }
);Meine Empfehlungen:
Array.prototype.forEach()Array.prototype.entries()Alle vorhandenen Web-APIs geben nur Zahlen zurück und akzeptieren nur Zahlen und werden nur von Fall zu Fall auf Bigint umgestellt.
Man könnte theoretisch number in integer und double aufteilen, aber das würde der Sprache viele neue Komplexitäten hinzufügen (mehrere reine Integer-Operatoren usw.). Ich habe die Konsequenzen in einem Gist skizziert.
Danksagungen