Wenn Number nicht groß genug ist
JavaScript-Numbers sind 64-Bit-Floats – und verlieren bei Ganzzahlen über 9 Billiarden (2^53) lautlos Präzision. BigInt löst das Problem: beliebig große Ganzzahlen ohne Rundungsfehler, für IDs, Kryptographie, Timestamps und Finanzberechnungen.
Inhaltsverzeichnis
- 1. Das Problem: Number verliert Präzision
- 2. Was ist BigInt und wie erstellt man ihn?
- 3. Arithmetische Operatoren mit BigInt
- 4. Typenkonvertierung: BigInt und Number mischen
- 5. BigInt und JSON: Serialisierungsprobleme lösen
- 6. Bitoperationen mit BigInt
- 7. BigInt in der Kryptographie
- 8. Performance: BigInt vs. Number
- 9. BigInt vs. Number: Vergleich der Grenzen
- 10. Zusammenfassung
- 11. FAQ
1. Das Problem: Number verliert Präzision
JavaScript hat nur einen numerischen Typ für Zahlen: Number. Er entspricht dem IEEE 754 Double-Precision-Format mit 64 Bit – davon 52 Bit für die Mantisse. Das bedeutet: Ganzzahlen können exakt dargestellt werden, solange sie kleiner als 2^53 (9.007.199.254.740.992) sind. Diese Grenze ist als Number.MAX_SAFE_INTEGER in der Sprache verankert. Jenseits dieser Grenze beginnt Rundung: Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2 ergibt true, weil beide Werte zum nächsten darstellbaren Float gerundet werden.
In der Praxis tritt dieses Problem bei Datenbank-IDs aus Twitter, Snowflake oder anderen verteilten Systemen auf – diese IDs sind häufig 64-Bit-Ganzzahlen und überschreiten Number.MAX_SAFE_INTEGER. Eine Twitter-ID wie 1591874983876853760 wird in JavaScript als Number gerundet und kann die falsche Ressource adressieren. Auch Timestamps in Nanosekunden, Kryptographie-Schlüssel, Blockchain-Werte und bestimmte Hash-Werte überschreiten die sichere Grenze. BigInt wurde in ES2020 als Antwort auf genau dieses Problem eingeführt.
2. Was ist BigInt und wie erstellt man ihn?
BigInt ist ein primitiver Typ in JavaScript, eingeführt in ES2020, der beliebig große Ganzzahlen mit exakter Präzision darstellt – begrenzt nur durch den verfügbaren Arbeitsspeicher. Es gibt zwei Wege, einen BigInt zu erstellen: durch das Literal-Suffix n oder durch die Funktion BigInt(). Das Literal 9007199254740993n ist ein BigInt, während 9007199254740993 ein Number ist, der gerundet wird. BigInt("9007199254740993") konvertiert einen String zu einem BigInt – das ist der sichere Weg, wenn die Zahl aus einer API kommt.
BigInt ist ein eigener primitiver Typ, erkennbar an typeof 1n === "bigint". Er verhält sich wie eine Ganzzahl – es gibt keine Dezimalstellen, kein Infinity, kein NaN. Division bei BigInt ist ganzzahlige Division: 5n / 2n === 2n (Rest wird abgeschnitten). Negative BigInts funktionieren wie erwartet: -5n / 2n === -2n. Es gibt BigInt.asIntN(width, value) und BigInt.asUintN(width, value), die den BigInt auf eine feste Bitbreite beschneiden – nützlich für die Simulation von 32-Bit- oder 64-Bit-Ganzzahlen.
// BigInt creation: literal suffix 'n' or BigInt() constructor
const fromLiteral = 9007199254740993n; // exact — no rounding
const fromNumber = BigInt(9007199254740992); // safe conversion from Number
const fromString = BigInt("9007199254740993456789"); // from API response
// typeof distinguishes BigInt from Number
console.log(typeof 42n); // "bigint"
console.log(typeof 42); // "number"
// Demonstrate the Number precision problem
console.log(9007199254740993 === 9007199254740994); // true — Number is wrong!
console.log(9007199254740993n === 9007199254740994n); // false — BigInt is correct
// BigInt division is integer division (truncates fractional part)
console.log(17n / 5n); // 3n — not 3.4
console.log(-17n / 5n); // -3n — truncates toward zero
// Clamping to fixed bit width (useful for simulation)
const u32max = BigInt.asUintN(32, 0xFFFFFFFFn + 1n);
console.log(u32max); // 0n — wraps around like uint32 overflow
3. Arithmetische Operatoren mit BigInt
Alle arithmetischen Grundoperatoren funktionieren mit BigInt: Addition (+), Subtraktion (-), Multiplikation (*), ganzzahlige Division (/), Modulo (%) und Potenzierung (**). Die Vergleichsoperatoren (<, >, ===, !==) funktionieren ebenfalls. Wichtig: Der einstellige +-Operator (unary plus) funktioniert mit BigInt nicht – das ist eine bewusste Designentscheidung, die versehentliche Konvertierungen zu Number verhindert.
Bitweise Operatoren (&, |, ^, ~, <<, >>) funktionieren alle mit BigInt. Der unsigned right shift (>>>) existiert für BigInt nicht, da BigInt kein festes Bitformat hat und der Begriff "unsigned" sinnlos wäre. Logische Operatoren (&&, ||) und der ternäre Operator funktionieren mit BigInt. 0n ist falsy, alle anderen BigInts sind truthy – konsistent mit den anderen JavaScript-Typen. Loser Vergleich mit == funktioniert zwischen BigInt und Number, strikter Vergleich mit === nicht, da die Typen verschieden sind.
4. Typenkonvertierung: BigInt und Number mischen
Das größte Stolperstein bei BigInt ist, dass JavaScript keine implizite Konvertierung zwischen BigInt und Number erlaubt. 1n + 1 wirft einen TypeError: Cannot mix BigInt and other types. Das ist eine bewusste Designentscheidung: Implizite Konvertierungen würden leise Präzisionsverluste verursachen und den Zweck von BigInt untergraben. Explizite Konvertierung ist immer erforderlich: Number(1n) oder BigInt(1).
Die Konvertierung von BigInt zu Number verliert Präzision, wenn der Wert Number.MAX_SAFE_INTEGER überschreitet – das ist der gleiche Fehler, den man zu vermeiden versucht. Daher sollte man immer prüfen, ob die Konvertierung sicher ist. BigInt(x) von einem Number funktioniert nur für ganzzahlige Werte: BigInt(1.5) wirft einen RangeError. Für den Vergleich mit Number kann man losen Vergleich verwenden: 1n == 1 ist true, weil JavaScript für == eine Konvertierungsregel hat, die BigInt und Number vergleichbar macht.
// Explicit conversion required — no implicit mixing
const big = 9007199254740993n;
// Safe: check before converting BigInt → Number
function bigIntToNumber(n) {
if (n > BigInt(Number.MAX_SAFE_INTEGER) || n < BigInt(Number.MIN_SAFE_INTEGER)) {
throw new RangeError(`BigInt ${n} exceeds safe integer range`);
}
return Number(n);
}
// Safe conversion from API string to BigInt
function parseId(idString) {
const parsed = BigInt(idString);
return parsed;
}
// Loose vs strict equality
console.log(1n == 1); // true — loose equality allows type coercion
console.log(1n === 1); // false — strict equality checks type
console.log(1n < 2); // true — comparison operators work across types
// Formatting large BigInt values
const trillion = 1_000_000_000_000n; // numeric separators work with BigInt
console.log(trillion.toString()); // "1000000000000"
console.log(trillion.toString(16)); // "e8d4a51000" — hex representation
console.log(trillion.toString(2)); // binary representation
5. BigInt und JSON: Serialisierungsprobleme lösen
BigInt kann nicht direkt mit JSON.stringify() serialisiert werden – es wirft einen TypeError: Do not know how to serialize a BigInt. Das ist eine bewusste Entscheidung, da JSON keinen nativen BigInt-Typ hat und eine stille Konvertierung zu Number wieder Präzision kosten würde. Die Lösung ist ein benutzerdefinierter Replacer in JSON.stringify() und ein benutzerdefinierter Reviver in JSON.parse().
Ein weit verbreitetes Muster: BigInt-Werte werden im JSON als Strings übertragen (mit einem Suffix wie "n" oder in einem Wrapper-Objekt). Beim Parsen werden diese Strings erkannt und zurück zu BigInt konvertiert. Alternativ gibt es die Bibliothek json-bigint, die das korrekte Parsen von großen Zahlen in JSON übernimmt – besonders nützlich wenn eine externe API große IDs als Zahlen (nicht als Strings) in JSON überträgt, was per JSON-Spezifikation erlaubt ist, in JavaScript aber Präzision kostet.
6. Bitoperationen mit BigInt
Einer der überraschenden Anwendungsfälle von BigInt sind Bitoperationen auf Werten, die 32 Bit überschreiten. JavaScript-Numbers führen bitweise Operationen auf 32-Bit-Ganzzahlen durch – Werte werden intern zu einem 32-Bit-Integer konvertiert, die Operation wird durchgeführt, und das Ergebnis wird wieder zu Number konvertiert. Das bedeutet: bitweise Operationen auf großen Zahlen mit Number verlieren Information jenseits der 32-Bit-Grenze.
Mit BigInt arbeiten bitweise Operationen auf beliebig breiten Werten, begrenzt nur durch den Speicher. Das ist relevant für Flags und Bitmasks, die mehr als 32 Bit benötigen, für 64-Bit-Flags aus systemnahen APIs, für Implementierungen von Hash-Funktionen, die mit 64-Bit-Werten arbeiten, und für Protokoll-Implementierungen, die mit großen Bitfeldern operieren. BigInt.asUintN(64, value) simuliert dabei eine 64-Bit-Ganzzahl-Arithmetik mit definiertem Overflow-Verhalten.
// Bitwise operations on large values — BigInt handles >32 bits correctly
const flags64 = 0b1000000000000000000000000000000000000000000000000000000000000001n;
// Set, check, clear bits
const BIT_ADMIN = 1n << 0n;
const BIT_WRITE = 1n << 1n;
const BIT_EXECUTE = 1n << 63n; // 64th bit — impossible with Number
let permissions = 0n;
permissions |= BIT_ADMIN; // set admin flag
permissions |= BIT_EXECUTE; // set execute flag
const isAdmin = (permissions & BIT_ADMIN) !== 0n; // true
const isExecute = (permissions & BIT_EXECUTE) !== 0n; // true
const isWrite = (permissions & BIT_WRITE) !== 0n; // false
// Simulate 64-bit unsigned arithmetic with defined overflow
function uint64Add(a, b) {
return BigInt.asUintN(64, a + b);
}
console.log(uint64Add(0xFFFFFFFFFFFFFFFFn, 1n)); // 0n — wraps to 0 like uint64
// FNV-1a hash (64-bit variant) implemented in BigInt
function fnv1a64(str) {
const PRIME = 1099511628211n;
const OFFSET = 14695981039346656037n;
let hash = OFFSET;
for (const char of str) {
hash ^= BigInt(char.charCodeAt(0));
hash = BigInt.asUintN(64, hash * PRIME);
}
return hash;
}
7. BigInt in der Kryptographie
Kryptographische Algorithmen wie RSA, Diffie-Hellman und elliptische Kurven arbeiten mit sehr großen Ganzzahlen – typischerweise 2048 bis 4096 Bit groß. Bevor BigInt in JavaScript verfügbar war, mussten Kryptobibliotheken eigene BigInteger-Implementierungen mitbringen (z.B. jsbn oder forge). Mit nativem BigInt sind diese Operationen direkt in JavaScript implementierbar, ohne externe Abhängigkeiten für die Grundarithmetik.
Für produktiven Einsatz in der Kryptographie sollte man jedoch die SubtleCrypto-API des Browsers bevorzugen, die native, seitenkanalresistente Implementierungen bietet. BigInt eignet sich für das Verstehen und Prototypen kryptographischer Algorithmen sowie für Anwendungsfälle, wo SubtleCrypto nicht ausreicht – etwa die Implementierung von Shamir's Secret Sharing, Pedersen-Commitments oder bestimmte Protokolle, die spezifische Primzahloperationen erfordern. Modular-Exponentiation – der Kern von RSA – ist mit BigInt und dem **-Operator und % direkt implementierbar.
8. Performance: BigInt vs. Number
BigInt-Arithmetik ist langsamer als Number-Arithmetik – das ist unvermeidbar, weil Number direkt auf IEEE 754 Hardware-Operationen abbildet, während BigInt beliebige Größen verwalten und Software-Multipräzisions-Arithmetik implementieren muss. In V8-Benchmarks (Chrome/Node.js) ist eine einfache BigInt-Addition etwa 5–50x langsamer als die gleiche Number-Operation, abhängig von der Größe der Operanden und ob der JIT-Compiler den Code optimieren konnte.
Das bedeutet: BigInt ist nicht für Anwendungen gedacht, die intensive numerische Berechnungen mit vielen Iterationen durchführen. Für Physik-Simulationen, Spiele-Engines und Signalverarbeitung bleibt Number (oder TypedArray) die richtige Wahl. BigInt ist für Präzision, nicht für Geschwindigkeit. In Szenarien, wo man korrekte Ergebnisse für große Ganzzahlen braucht und die Anzahl der Operationen überschaubar ist – ID-Verarbeitung, Protokoll-Implementierung, gelegentliche Hash-Berechnungen – ist der Performanceunterschied irrelevant.
9. BigInt vs. Number: Vergleich der Grenzen
Die Wahl zwischen BigInt und Number ist keine persönliche Präferenz, sondern eine sachliche Entscheidung basierend auf den Anforderungen. Wenn die zu verarbeitenden Ganzzahlen garantiert unter 2^53 bleiben, ist Number korrekt und deutlich performanter. Wenn nicht – oder wenn man nicht sicher sein kann – ist BigInt die sichere Wahl.
| Eigenschaft | Number | BigInt | Empfehlung |
|---|---|---|---|
| Maximale sichere Ganzzahl | 2^53 − 1 | Unbegrenzt | BigInt für IDs >2^53 |
| Dezimalstellen | Ja (Float) | Nein (nur Ganzzahl) | Number für Fließkomma |
| JSON-Serialisierung | Direkt | Manuell (Replacer) | BigInt als String übertragen |
| Performance | Sehr schnell (Hardware) | 5–50x langsamer | Number bei Massenoperationen |
| Bitoperationen >32 Bit | Verlieren Bits | Exakt | BigInt für 64-Bit-Flags |
BigInt und Number ergänzen sich: Number für Fließkomma-Mathematik, Performance-kritische Berechnungen und die überwältigende Mehrheit numerischer Operationen. BigInt für korrekte Darstellung großer Ganzzahlen, 64-Bit-Flags, Kryptographie-Prototypen und Szenarien, wo ein einziger Rundungsfehler fatale Konsequenzen hätte. Die beiden Typen können nicht gemischt werden – was anfangs frustrierend wirkt, aber explizite Konvertierungen erzwingt und so versehentliche Präzisionsverluste verhindert.
Mironsoft
JavaScript-Entwicklung, Datenintegrität und sichere ID-Verarbeitung
Präzisionsprobleme mit großen Zahlen in Ihrer Anwendung beheben?
Wir analysieren bestehende Systeme auf versteckte Präzisionsverluste bei ID-Verarbeitung, Timestamps und numerischen Berechnungen – und migrieren auf BigInt wo nötig.
Precision-Audit
Analyse auf versteckte Number-Präzisionsverluste bei IDs, Timestamps und Finanzdaten
BigInt-Migration
Schrittweise Migration von Number zu BigInt inkl. JSON-Serialisierung und API-Adapter
Code-Review
Review auf implizite BigInt/Number-Mischungen und fehlerhafte Konvertierungslogik
10. Zusammenfassung
BigInt ist die Antwort von JavaScript auf das seit ES1 bestehende Problem, dass Number keine beliebig großen Ganzzahlen exakt darstellen kann. Mit dem Suffix n oder der Funktion BigInt() erstellt man BigInt-Werte, die alle arithmetischen Operatoren und bitweisen Operatoren unterstützen. Implizite Konvertierungen zwischen BigInt und Number sind verboten – explizite Konvertierungen sind immer erforderlich. Für JSON-Serialisierung sind benutzerdefinierte Replacer und Reviver notwendig. Performance ist schlechter als Number, aber das ist der bewusste Kompromiss für exakte Präzision.
Die typischen Einsatzgebiete sind klar: BigInt für Twitter-/Snowflake-IDs und andere 64-Bit-IDs aus externen Systemen, für Timestamps in Nanosekunden, für 64-Bit-Flags und Bitmasks, für kryptographische Berechnungen und für alle Szenarien, wo Rundungsfehler bei großen Ganzzahlen inakzeptabel sind. Number bleibt die richtige Wahl für Fließkomma-Berechnungen, Performance-kritische Schleifen und die große Mehrheit aller numerischen Operationen in JavaScript-Anwendungen.
BigInt in JavaScript — Das Wichtigste auf einen Blick
Wann BigInt
Ganzzahlen über 2^53, 64-Bit-IDs aus externen Systemen, Kryptographie, 64-Bit-Flags. Nicht für Fließkomma oder Performance-kritische Massenoperationen.
Syntax
Literal: 9007199254740993n — Suffix n. Aus String: BigInt("123456789012345678"). Aus Number nur für ganzzahlige, sichere Werte.
Konvertierung
Keine implizite Mischung mit Number — TypeError. Explizit: Number(bigint) prüft Sicherheitsgrenze. BigInt(number) nur für ganzzahlige Numbers.
JSON
JSON.stringify wirft TypeError für BigInt. Lösung: Replacer der BigInt als String serialisiert, Reviver der zurückkonvertiert.