JavaScript hat nur einen numerischen Typ für Integer und Float — beide werden als 64-Bit-IEEE-754-Double gespeichert. Das ist pragmatisch, hat aber Konsequenzen: Genauigkeitsverluste bei Brüchen, eine harte Sicherheits-Grenze bei 2^53 - 1 und Sonderwerte wie NaN und Infinity. Mit ES2020 kam BigInt als zweites numerisches Primitive — eine Ganzzahl ohne Längen-Begrenzung, gekennzeichnet durch das Suffix n. Beide Typen sind nicht kompatibel: jede Mischung wirft TypeError. Dieser Artikel erklärt das Speicher-Modell, die Sicherheits-Grenzen und wann welches Primitive das richtige ist.
Number ist ein 64-Bit-Double
Anders als C, Java oder Rust kennt JavaScript keine Trennung in int, long, float und double. Jeder numerische Wert — 42, 3.14, -0, Infinity — ist ein einziger Typ: number. Im Speicher liegt dahinter immer ein IEEE-754-Double mit 64 Bit, aufgeteilt in 1 Vorzeichen-Bit, 11 Exponent-Bits und 52 Mantisse-Bits.
Das hat zwei wichtige Folgen. Erstens: ganzzahlige Werte sind nur bis 2^53 - 1 exakt darstellbar — danach beginnt der Mantisse-Verlust. Zweitens: Brüche, die im Dezimal-System endlich aussehen (0.1, 0.2), haben im Binär-System eine periodische Darstellung und werden gerundet gespeichert. Beide Eigenheiten erzeugen die klassischen JavaScript-Zahl-Bugs.
// ein Typ für alles
typeof 42; // 'number'
typeof 3.14; // 'number'
typeof -0; // 'number'
typeof NaN; // 'number'
typeof Infinity;// 'number'
// intern alles IEEE-754-Double
console.log(42 === 42.0); // true — kein UnterschiedtrueMAX_SAFE_INTEGER und Friends
Die wichtigste Konstante für Integer-Arithmetik ist Number.MAX_SAFE_INTEGER — exakt 2^53 - 1 = 9_007_199_254_740_991. Bis zu dieser Grenze ist garantiert, dass jede Ganzzahl bit-genau dargestellt wird und n + 1 !== n gilt. Darüber bricht die Eindeutigkeit zusammen.
Number.MAX_SAFE_INTEGER; // 9007199254740991
Number.MIN_SAFE_INTEGER; // -9007199254740991
// ab hier wird es ungenau
2 ** 53; // 9007199254740992
2 ** 53 + 1; // 9007199254740992 (!)
2 ** 53 + 2; // 9007199254740994
Number.isSafeInteger(2 ** 53); // false
Number.isSafeInteger(2 ** 53 - 1); // true9007199254740991
trueDaneben gibt es Number.MAX_VALUE (~1,8 × 10^308) als absoluten Float-Maximum und Number.EPSILON (2^-52) als kleinsten Abstand zwischen 1 und der nächsten darstellbaren Zahl — wichtig für Vergleiche von Float-Ergebnissen.
NaN, Infinity und -0
Drei Sonderwerte gehören zu jedem IEEE-754-Double und damit zum Number-Typ in JavaScript. NaN entsteht bei undefinierten Operationen (0/0, Math.sqrt(-1), Number('abc')). Infinity und -Infinity entstehen bei Overflow oder Division durch null. -0 ist ein eigenes Bit-Muster, im Vergleich aber praktisch identisch zu 0.
NaN === NaN; // false — NaN ist nie gleich sich selbst
Number.isNaN(NaN); // true — strikte Prüfung
isNaN('abc'); // true — Coercion! 'abc' -> NaN
Number.isNaN('abc'); // false — strikte Prüfung, kein Coercion
1 / 0; // Infinity
-1 / 0; // -Infinity
Number.isFinite(1/0); // false
0 === -0; // true — Vergleich identisch
Object.is(0, -0); // false — Bit-Muster verschieden
1 / -0; // -Infinity — hier zeigt sich der Unterschiedfalse
true
trueDer berühmte 0.1 + 0.2-Bug
Der bekannteste JavaScript-„Bug" ist gar keiner — er ist eine Eigenschaft jedes IEEE-754-Systems. 0.1 und 0.2 haben binär keine endliche Darstellung, beide werden gerundet, und die Summe der gerundeten Werte ist nicht exakt 0.3.
0.1 + 0.2; // 0.30000000000000004
0.1 + 0.2 === 0.3; // false
// Workaround 1: Epsilon-Vergleich
const nearlyEqual = (a, b) => Math.abs(a - b) < Number.EPSILON;
nearlyEqual(0.1 + 0.2, 0.3); // true
// Workaround 2: feste Dezimalstellen
(0.1 + 0.2).toFixed(2); // '0.30'
// Workaround 3: in Cents/Mille rechnen, am Ende dividieren
const total = (10 + 20) / 100; // exakt 0.3
// Workaround 4: BigInt für Geld (skalierte Ganzzahlen)
const cents = 10n + 20n; // 30n0.30000000000000004
false
trueFür Geld-Berechnungen ist die Lehre eindeutig: niemals direkt mit Float-Number rechnen. Entweder in Cents (Ganzzahl), als BigInt oder mit einer Decimal-Library (decimal.js, big.js).
Literale: Decimal, Hex, Octal, Binary
JavaScript erlaubt vier Literal-Schreibweisen für Number, dazu seit ES2021 Numeric Separators mit Unterstrich für bessere Lesbarkeit großer Zahlen.
const dezimal = 42;
const hex = 0xff; // 255
const oktal = 0o755; // 493 (Unix-Permissions)
const binaer = 0b1010; // 10
// Numeric Separators (ES2021) — rein optisch
const million = 1_000_000;
const speicher = 0xff_ff_ff;
const credit = 4_242_4242_4242_4242n; // auch in BigInt
// Float-Literale
const wissen = 1.5e3; // 1500
const winzig = 5e-7; // 0.00000051000000Der Unterstrich darf nur zwischen Ziffern stehen, nicht am Anfang/Ende oder doppelt — der Parser ist hier strikt. Beim Parsen aus Strings (Number('1_000')) wird der Unterstrich nicht akzeptiert, das ist nur Source-Syntax.
BigInt: Ganzzahl ohne Grenze
Mit ES2020 bekam JavaScript ein zweites numerisches Primitive: BigInt. Es speichert Ganzzahlen in beliebiger Größe — limitiert nur durch den verfügbaren Speicher. Die Syntax ist ein Number-Literal mit Suffix n oder der BigInt()-Konstruktor.
const a = 42n; // Literal
const b = BigInt(42); // aus Number
const c = BigInt('9007199254740993');// aus String
typeof 42n; // 'bigint' — eigener typeof-Wert
// beliebig groß
const huge = 2n ** 200n;
// 1606938044258990275541962092341162602522202993782792835301376n
// Division trunkiert (kein Float-Ergebnis)
7n / 2n; // 3n (nicht 3.5)
7n % 2n; // 1nbigint
3nMix verboten: Konvertierung Pflicht
Die Sprache verbietet jede arithmetische Mischung der beiden Typen. 1n + 1 wirft TypeError. Hintergrund: BigInt soll exakt sein, Number ist es nicht — eine implizite Konvertierung würde die Garantie unterwandern. Der Mix muss explizit erfolgen.
// Mix wirft TypeError
// 1n + 1; // TypeError: Cannot mix BigInt and other types
// explizite Konvertierung
Number(42n); // 42 — Achtung: Precision-Verlust ab 2^53
BigInt(42); // 42n — wirft RangeError bei Float-Input
// BigInt(3.14); // RangeError — kein Float erlaubt
// BigInt(NaN); // RangeError
// Vergleiche dürfen mischen
1n === 1; // false — Typ verschieden
1n == 1; // true — loose equality coerced
1n < 2; // true
2n > 1; // true
// Sortieren von gemischten Arrays funktioniert
[4n, 6, -12n, 10, 0n].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
// [-12n, 0n, 4n, 6, 10]42
trueWofür BigInt wirklich da ist
BigInt ist kein Universal-Ersatz für Number — die Performance-Kosten sind real (siehe nächste Section). Es hat aber klare Anwendungsfälle, in denen Number versagt.
- Datenbank-IDs jenseits 2^53: Twitter Snowflake-IDs, PostgreSQL
bigserial, Discord-Snowflakes — alle 64-Bit-Integer, die in JSON beim Parsen mitJSON.parsePrecision verlieren würden, wenn man sie in Number zwingt. - Hochauflösende Timestamps:
process.hrtime.bigint()in Node liefert Nanosekunden seit Prozess-Start als BigInt — für Performance-Messung essentiell. - Krypto-Operationen: RSA-Schlüssel, große Primzahlen, modulare Exponentiation. Die Web-Crypto-API gibt teilweise BigInt-Werte zurück.
- Skalierte Geld-Werte: Beträge in Cents oder Tausendstel als BigInt — exakte Arithmetik ohne Float-Drift.
// Snowflake-ID aus Twitter-API
const tweetId = '1234567890123456789'; // String aus JSON
const id = BigInt(tweetId); // exakt erhalten
// const broken = Number(tweetId); // 1234567890123456800 — kaputt!
// Nanosekunden-Timer (Node)
// const t0 = process.hrtime.bigint();
// ... Operation ...
// const t1 = process.hrtime.bigint();
// const ns = t1 - t0; // BigInt-Differenz
// skalierte Geld-Beträge in Cents
const preisCent = 1999n; // 19,99 EUR
const steuer = preisCent * 19n / 100n;
const brutto = preisCent + steuer; // exakt2378nNumber ist deutlich schneller
BigInt ist heap-alloziert und kann nicht von der SMI-Optimierung der Engine profitieren. Jede arithmetische Operation auf BigInt erzeugt ein neues Objekt; bei großen Werten kommt zusätzlich der Aufwand für Multi-Word-Arithmetik hinzu. In Microbenchmarks ist Number-Arithmetik je nach Operation 5-50× schneller.
| Aspekt | Number | BigInt |
|---|---|---|
| Speicher | 64 Bit (SMI: 31 Bit) | Heap-allokiert, beliebig groß |
| Wertebereich Integer | ±(2^53 - 1) exakt | unbegrenzt |
| Float / Brüche | ja (mit Precision-Verlust) | nein, nur Ganzzahl |
| Performance | sehr schnell | 5-50× langsamer |
typeof | 'number' | 'bigint' |
| JSON-Serialisierung | nativ | wirft TypeError |
| Math-Methoden | ja | nein (Math.sqrt(4n) -> Error) |
| Mix mit anderem Typ | n/a | TypeError bei Arithmetik |
| Literal-Suffix | keiner | n (z. B. 42n) |
Typische Fallen mit Zahlen
0.1 + 0.2 ergibt 0.30000000000000004 — IEEE-754, kein Bug
Das ist die Eigenheit jedes Float-Systems, kein JavaScript-Spezifikum. Java, Python, C — alle zeigen dasselbe Verhalten. Konsequenz: finanzielle Berechnungen niemals direkt mit Number. Stattdessen in Cents (Integer), in BigInt (skalierte Ganzzahlen) oder mit einer Decimal-Library (decimal.js, big.js) rechnen.
Number.isNaN(x) vs. globales isNaN(x) — strikt vs. Coercion
Das globale isNaN('abc') ist true, weil 'abc' erst zu NaN gecoerced wird. Number.isNaN('abc') ist false — strikte Prüfung ohne Konvertierung. In modernem Code immer die Number.-Variante nutzen, das globale isNaN ist Legacy.
BigInt + Number wirft TypeError — Mix verboten
1n + 1 bricht mit TypeError: Cannot mix BigInt and other types. Der Mix muss explizit erfolgen: 1n + BigInt(1) oder Number(1n) + 1. Vergleichs-Operatoren (<, ==) dürfen aber mischen — das ist eine bewusste Asymmetrie der Spezifikation.
JSON.stringify(1n) wirft TypeError
BigInt ist nicht JSON-serialisierbar. JSON.stringify({ id: 1n }) wirft TypeError: Do not know how to serialize a BigInt. Workaround: einen replacer übergeben, der BigInt zu String konvertiert, oder BigInt.prototype.toJSON definieren. Beim Parsen muss der Receiver wissen, dass er den String zurück zu BigInt wandeln soll.
Math-Funktionen akzeptieren keine BigInt
Math.sqrt(4n) wirft TypeError. Das gesamte Math-Objekt arbeitet ausschließlich mit Number. Wer Wurzeln, Logarithmen oder trigonometrische Funktionen auf BigInt braucht, muss vorher konvertieren (mit Precision-Verlust) oder eine spezialisierte Library nutzen.
typeof 1n === 'bigint' — eigener typeof-Wert
BigInt war das erste neue Primitive seit Symbol (ES6) und bekam einen eigenen typeof. Wer in alten typeof-Switches nur 'number' berücksichtigt, übersieht BigInt-Werte stillschweigend — etwa in generischen Validatoren oder Serializern.
2**53 + 1 === 2**53 — Precision-Verlust bei großen IDs
Ab Number.MAX_SAFE_INTEGER (2^53 - 1) verliert Number-Arithmetik die Eindeutigkeit. Datenbank-IDs aus PostgreSQL bigserial, Twitter Snowflake oder Discord-Snowflakes liegen oft darüber. Werden sie aus JSON mit JSON.parse in Number gelesen, sind die letzten Ziffern stillschweigend kaputt. Lösung: API-seitig als String übertragen, client-seitig in BigInt parsen.
Math.round(-1.5) ist -1, nicht -2 — Round-Half-Away-From-Zero only positiv
Math.round rundet positive Halben aufwärts (1.5 -> 2), negative Halben aber Richtung null (-1.5 -> -1). Das ist asymmetrisch und überrascht regelmäßig. Wer symmetrisches Runden braucht, nutzt Math.sign(x) * Math.round(Math.abs(x)) oder eine eigene Funktion.
Weiterführende Ressourcen
Externe Quellen
- Number – MDN
- BigInt – MDN
- IEEE 754 – Wikipedia
- What Every Computer Scientist Should Know About Floating-Point Arithmetic