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.

JavaScript number-grundlagen.js
// 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 Unterschied
Output
true

MAX_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.

JavaScript safe-integer.js
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); // true
Output
9007199254740991
true

Daneben 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.

JavaScript spezielle-werte.js
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 Unterschied
Output
false
true
true

Der 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.

JavaScript float-genauigkeit.js
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;        // 30n
Output
0.30000000000000004
false
true

Fü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.

JavaScript literale.js
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.0000005
Output
1000000

Der 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.

JavaScript bigint-grundlagen.js
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;                             // 1n
Output
bigint
3n

Mix 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.

JavaScript mix-konvertierung.js
// 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]
Output
42
true

Wofü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 mit JSON.parse Precision 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.
JavaScript use-cases.js
// 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;    // exakt
Output
2378n

Number 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.

AspektNumberBigInt
Speicher64 Bit (SMI: 31 Bit)Heap-allokiert, beliebig groß
Wertebereich Integer±(2^53 - 1) exaktunbegrenzt
Float / Brücheja (mit Precision-Verlust)nein, nur Ganzzahl
Performancesehr schnell5-50× langsamer
typeof'number''bigint'
JSON-Serialisierungnativwirft TypeError
Math-Methodenjanein (Math.sqrt(4n) -> Error)
Mix mit anderem Typn/aTypeError bei Arithmetik
Literal-Suffixkeinern (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

/ Weiter

Zurück zu Datentypen

Zur Übersicht