Kapitel 11. Zahlen
Inhaltsverzeichnis
Das Buch kaufen
(Werbung, bitte nicht blockieren.)

Kapitel 11. Zahlen

JavaScript hat einen einzigen Typ für alle Zahlen: es behandelt sie alle als Fließkommazahlen. Der Punkt wird jedoch nicht angezeigt, wenn keine Ziffern nach dem Dezimalpunkt stehen:

> 5.000
5

Intern optimieren die meisten JavaScript-Engines und unterscheiden zwischen Fließkommazahlen und ganzen Zahlen (Details: Ganze Zahlen in JavaScript). Aber das ist etwas, das Programmierer nicht sehen.

JavaScript-Zahlen sind double (64-Bit)-Werte, basierend auf dem IEEE-Standard für Fließkomma-Arithmetik (IEEE 754). Dieser Standard wird von vielen Programmiersprachen verwendet.

Zahlenliterale

Ein Zahlenliteral kann eine ganze Zahl, eine Fließkommazahl oder eine (ganzzahlige) Hexadezimalzahl sein:

> 35  // integer
35
> 3.141  // floating point
3.141
> 0xFF  // hexadecimal
255

Exponent

Ein Exponent, eX, ist eine Abkürzung für „multipliziere mit 10X“:

> 5e2
500
> 5e-2
0.05
> 0.5e2
50

Aufrufen von Methoden auf Literalen

Bei Zahlenliteralen muss der Punkt für den Zugriff auf eine Eigenschaft vom Dezimalpunkt unterschieden werden. Dies gibt Ihnen die folgenden Optionen, wenn Sie toString() auf dem Zahlenliteral 123 aufrufen möchten:

123..toString()
123 .toString()  // space before the dot
123.0.toString()
(123).toString()

Konvertierung in eine Zahl

Werte werden wie folgt in Zahlen umgewandelt:

WertErgebnis

undefined

NaN

null

0

Ein boolescher Wert

false0

true1

Eine Zahl

Identisch mit der Eingabe (keine Konvertierung erforderlich)

Ein String

Parsen der Zahl in der Zeichenkette (ignorieren von führenden und nachfolgenden Leerzeichen); die leere Zeichenkette wird in 0 umgewandelt. Beispiel: '3.141'3.141

Ein Objekt

Aufruf von ToPrimitive(value, Number) (siehe Algorithmus: ToPrimitive()—Konvertierung eines Werts in ein Primitiv) und Konvertierung des resultierenden Primitivs.

Bei der Konvertierung der leeren Zeichenkette in eine Zahl wäre NaN arguably das bessere Ergebnis. Das Ergebnis 0 wurde gewählt, um bei leeren numerischen Eingabefeldern zu helfen, im Einklang damit, was andere Programmiersprachen Mitte der 1990er Jahre taten.[14]

Manuelle Konvertierung in eine Zahl

Die beiden gebräuchlichsten Arten, einen beliebigen Wert in eine Zahl umzuwandeln, sind:

Number(value)

(Als Funktion aufgerufen, nicht als Konstruktor)

+value

Ich bevorzuge Number(), da es aussagekräftiger ist. Hier sind einige Beispiele

> Number('')
0
> Number('123')
123
> Number('\t\v\r12.34\n ')  // ignores leading and trailing whitespace
12.34

> Number(false)
0
> Number(true)
1

parseFloat()

Die globale Funktion parseFloat() bietet eine weitere Möglichkeit, Werte in Zahlen umzuwandeln. Allerdings ist Number() normalerweise die bessere Wahl, wie wir gleich sehen werden. Dieser Code:

parseFloat(str)

konvertiert str in eine Zeichenkette, entfernt führende Leerzeichen und parst dann das längste Präfix, das eine Fließkommazahl ist. Wenn kein solches Präfix existiert (z. B. in einer leeren Zeichenkette), wird NaN zurückgegeben.

Vergleich von parseFloat() und Number()

  • Die Anwendung von parseFloat() auf eine Nicht-Zeichenkette ist weniger effizient, da sie ihr Argument vor dem Parsen in eine Zeichenkette umwandelt. Infolgedessen werden viele Werte, die Number() in tatsächliche Zahlen umwandelt, von parseFloat() in NaN umgewandelt.

    > parseFloat(true)  // same as parseFloat('true')
    NaN
    > Number(true)
    1
    
    > parseFloat(null)  // same as parseFloat('null')
    NaN
    > Number(null)
    0
  • parseFloat() parst die leere Zeichenkette als NaN

    > parseFloat('')
    NaN
    > Number('')
    0
  • parseFloat() parst bis zum letzten gültigen Zeichen, was bedeutet, dass Sie ein Ergebnis erhalten, wo Sie es vielleicht nicht wollen.

    > parseFloat('123.45#')
    123.45
    > Number('123.45#')
    NaN
  • parseFloat() ignoriert führende Leerzeichen und stoppt vor ungültigen Zeichen (einschließlich Leerzeichen).

    > parseFloat('\t\v\r12.34\n ')
    12.34

    Number() ignoriert sowohl führende als auch nachfolgende Leerzeichen (aber andere ungültige Zeichen führen zu NaN).

Spezielle Zahlenwerte

JavaScript hat mehrere spezielle Zahlenwerte:

  • Zwei Fehlerwerte, NaN und Infinity.
  • Zwei Werte für Null, +0 und -0. JavaScript hat zwei Nullen, eine positive Null und eine negative Null, da das Vorzeichen und der Betrag einer Zahl getrennt gespeichert werden. In den meisten Teilen dieses Buches tue ich so, als gäbe es nur eine einzige Null, und Sie werden in JavaScript fast nie sehen, dass es zwei davon gibt.

NaN

Der Fehlerwert NaN (eine Abkürzung für „not a number“) ist ironischerweise ein Zahlenwert:

> typeof NaN
'number'

Er wird durch Fehler wie die folgenden erzeugt

  • Eine Zahl konnte nicht geparst werden

    > Number('xyz')
    NaN
    > Number(undefined)
    NaN
  • Eine Operation ist fehlgeschlagen

    > Math.acos(2)
    NaN
    > Math.log(-1)
    NaN
    > Math.sqrt(-1)
    NaN
  • Einer der Operanden ist NaN (dies stellt sicher, dass Sie bei einem Fehler während einer längeren Berechnung ihn im Endergebnis sehen können)

    > NaN + 3
    NaN
    > 25 / NaN
    NaN

Fallstrick: Überprüfung, ob ein Wert NaN ist

NaN ist der einzige Wert, der nicht gleich sich selbst ist:

> NaN === NaN
false

Strikte Gleichheit (===) wird auch von Array.prototype.indexOf verwendet. Sie können daher NaN mit dieser Methode nicht in einem Array suchen.

> [ NaN ].indexOf(NaN)
-1

Wenn Sie prüfen möchten, ob ein Wert NaN ist, müssen Sie die globale Funktion isNaN() verwenden:

> isNaN(NaN)
true
> isNaN(33)
false

Allerdings funktioniert isNaN nicht richtig mit Nicht-Zahlen, da es diese zuerst in Zahlen umwandelt. Diese Umwandlung kann NaN ergeben und dann gibt die Funktion fälschlicherweise true zurück.

> isNaN('xyz')
true

Daher ist es am besten, isNaN mit einer Typüberprüfung zu kombinieren:

function myIsNaN(value) {
    return typeof value === 'number' && isNaN(value);
}

Alternativ können Sie prüfen, ob der Wert ungleich sich selbst ist (da NaN der einzige Wert mit diesem Merkmal ist). Aber das ist weniger selbsterklärend:

function myIsNaN(value) {
    return value !== value;
}

Beachten Sie, dass dieses Verhalten durch IEEE 754 diktiert wird. Wie in Abschnitt 7.11, „Details von Vergleichsprädikaten“ erwähnt:[15]

Jedes NaN soll ungeordnet mit allem, einschließlich sich selbst, verglichen werden.

Infinity

Infinity ist ein Fehlerwert, der eines von zwei Problemen anzeigt: eine Zahl kann nicht dargestellt werden, weil ihr Betrag zu groß ist, oder eine Division durch Null ist erfolgt.

Infinity ist größer als jede andere Zahl (außer NaN). Ebenso ist -Infinity kleiner als jede andere Zahl (außer NaN). Das macht sie nützlich als Standardwerte – zum Beispiel, wenn Sie nach einem Minimum oder Maximum suchen.

Fehler: Der Betrag einer Zahl ist zu groß

Wie groß der Betrag einer Zahl sein kann, wird durch ihre interne Darstellung bestimmt (wie in Die interne Darstellung von Zahlen diskutiert), die das arithmetische Produkt von ist

  • Eine Mantisse (eine Binärzahl 1.f1f2...)
  • 2 hoch eine Exponent

Der Exponent muss zwischen −1023 und 1024 (ausschließlich) liegen. Wenn der Exponent zu klein ist, wird die Zahl 0. Wenn der Exponent zu groß ist, wird sie Infinity. 21023 kann noch dargestellt werden, aber 21024 nicht.

> Math.pow(2, 1023)
8.98846567431158e+307
> Math.pow(2, 1024)
Infinity

Fehler: Division durch Null

Die Division durch Null ergibt Infinity als Fehlerwert:

> 3 / 0
Infinity
> 3 / -0
-Infinity

Rechnen mit Infinity

Sie erhalten das Fehlerergebnis NaN, wenn Sie versuchen, eine Infinity mit einer anderen zu „neutralisieren“:

> Infinity - Infinity
NaN
> Infinity / Infinity
NaN

Wenn Sie versuchen, über Infinity hinauszugehen, erhalten Sie immer noch Infinity.

> Infinity + Infinity
Infinity
> Infinity * Infinity
Infinity

Prüfung auf Infinity

Strikte und tolerante Gleichheit funktionieren für Infinity einwandfrei:

> var x = Infinity;
> x === Infinity
true

Zusätzlich erlaubt die globale Funktion isFinite() Ihnen zu überprüfen, ob ein Wert eine tatsächliche Zahl ist (weder unendlich noch NaN):

> isFinite(5)
true
> isFinite(Infinity)
false
> isFinite(NaN)
false

Zwei Nullen

Da die Zahlen von JavaScript Betrag und Vorzeichen getrennt halten, hat jede nicht-negative Zahl ein negatives Gegenstück, einschließlich 0.

Der Grund dafür ist, dass bei der digitalen Darstellung einer Zahl diese so klein werden kann, dass sie von 0 nicht zu unterscheiden ist, da die Kodierung nicht präzise genug ist, um den Unterschied darzustellen. Dann erlaubt ein vorzeichenbehaftetes Null die Aufzeichnung „aus welcher Richtung“ man sich Null genähert hat; das heißt, welches Vorzeichen die Zahl hatte, bevor sie als Null betrachtet wurde. Wikipedia fasst die Vor- und Nachteile von vorzeichenbehafteten Nullen schön zusammen.

Es wird behauptet, dass die Aufnahme von vorzeichenbehaftetem Null in IEEE 754 die numerische Genauigkeit bei einigen kritischen Problemen, insbesondere bei Berechnungen mit komplexen elementaren Funktionen, erheblich erleichtert. Auf der anderen Seite widerspricht das Konzept der vorzeichenbehafteten Null der allgemeinen Annahme in den meisten mathematischen Bereichen (und in den meisten mathematischen Kursen), dass negative Null dasselbe ist wie Null. Darstellungen, die negative Null zulassen, können eine Fehlerquelle in Programmen sein, da Softwareentwickler nicht erkennen (oder vergessen), dass die beiden Null-Darstellungen zwar bei numerischen Vergleichen gleich funktionieren, aber unterschiedliche Bitmuster sind und in einigen Operationen unterschiedliche Ergebnisse liefern.

Beste Praxis: So tun, als gäbe es nur eine Null

JavaScript tut viel, um zu verbergen, dass es zwei Nullen gibt. Da es normalerweise keine Rolle spielt, dass sie unterschiedlich sind, wird empfohlen, dass Sie sich der Illusion der einzelnen Null anschließen. Untersuchen wir, wie diese Illusion aufrechterhalten wird.

In JavaScript schreiben Sie normalerweise 0, was +0 bedeutet. Aber -0 wird auch einfach als 0 angezeigt. Das sehen Sie, wenn Sie eine Browser-Befehlszeile oder die Node.js REPL verwenden.

> -0
0

Das liegt daran, dass die Standardmethode toString() beide Nullen in dieselbe '0' umwandelt:

> (-0).toString()
'0'
> (+0).toString()
'0'

Gleichheit unterscheidet Nullen ebenfalls nicht. Nicht einmal ===:

> +0 === -0
true

Array.prototype.indexOf verwendet ===, um nach Elementen zu suchen, und wahrt so die Illusion.

> [ -0, +0 ].indexOf(+0)
0
> [ +0, -0 ].indexOf(-0)
0

Die Ordnungsoperatoren betrachten die Nullen ebenfalls als gleich.

> -0 < +0
false
> +0 < -0
false

Unterscheidung der beiden Nullen

Wie kann man tatsächlich beobachten, dass die beiden Nullen unterschiedlich sind? Man kann durch Null teilen (-Infinity und +Infinity können durch === unterschieden werden):

> 3 / -0
-Infinity
> 3 / +0
Infinity

Eine weitere Möglichkeit, die Division durch Null durchzuführen, ist über Math.pow() (siehe Numerische Funktionen)

> Math.pow(-0, -1)
-Infinity
> Math.pow(+0, -1)
Infinity

Math.atan2() (siehe Trigonometrische Funktionen) enthüllt ebenfalls, dass die Nullen unterschiedlich sind:

> Math.atan2(-0, -1)
-3.141592653589793
> Math.atan2(+0, -1)
3.141592653589793

Die kanonische Methode zur Unterscheidung der beiden Nullen ist die Division durch Null. Daher würde eine Funktion zur Erkennung negativer Nullen wie folgt aussehen:

function isNegativeZero(x) {
    return x === 0 && (1/x < 0);
}

Hier ist die Funktion in Aktion.

> isNegativeZero(0)
false
> isNegativeZero(-0)
true
> isNegativeZero(33)
false

Die interne Darstellung von Zahlen

JavaScript-Zahlen haben eine 64-Bit-Präzision, die auch als doppelte Präzision bezeichnet wird (Typ double in einigen Programmiersprachen). Die interne Darstellung basiert auf dem IEEE-754-Standard. Die 64 Bits werden auf Vorzeichen, Exponenten und Bruchteil einer Zahl wie folgt verteilt:

VorzeichenExponent ∈ [−1023, 1024]Bruchteil

1 Bit

11 Bits

52 Bits

Bit 63

Bits 62–52

Bits 51–0

Der Wert einer Zahl wird mit der folgenden Formel berechnet

(–1)sign × %1.fraction × 2exponent

Das vorangestellte Prozentzeichen (%) bedeutet, dass die Zahl in der Mitte in Binärdarstellung geschrieben ist: eine 1, gefolgt von einem Binärpunkt, gefolgt von einem Binärbruch – nämlich den Binärziffern des Bruchteils (eine natürliche Zahl). Hier sind einige Beispiele dieser Darstellung:

+0

(Vorzeichen = 0, Bruchteil = 0, Exponent = −1023)

–0

(Vorzeichen = 1, Bruchteil = 0, Exponent = −1023)

1

= (−1)0 × %1.0 × 20

(Vorzeichen = 0, Bruchteil = 0, Exponent = 0)

2

= (−1)0 × %1.0 × 21

3

= (−1)0 × %1.1 × 21

(Vorzeichen = 0, Bruchteil = 251, Exponent = 0)

0.5

= (−1)0 × %1.0 × 2−1

−1

= (−1)1 × %1.0 × 20

Die Kodierungen von +0, −0 und 3 lassen sich wie folgt erklären:

  • ±0: Da der Bruchteil immer mit einer 1 präfixiert ist, ist es unmöglich, 0 damit darzustellen. Daher kodiert JavaScript eine Null über den Bruchteil 0 und den speziellen Exponenten −1023. Das Vorzeichen kann entweder positiv oder negativ sein, was bedeutet, dass JavaScript zwei Nullen hat (siehe Zwei Nullen).
  • 3: Bit 51 ist das höchstwertige (höchste) Bit des Bruchteils. Dieses Bit ist 1.

Spezielle Exponenten

Die zuvor erwähnte Darstellung von Zahlen wird als normalisiert bezeichnet. In diesem Fall liegt der Exponent e im Bereich −1023 < e < 1024 (untere und obere Grenzen ausgeschlossen). −1023 und 1024 sind spezielle Exponenten:

  • 1024 wird für Fehlerwerte wie NaN und Infinity verwendet.
  • −1023 wird verwendet für

    • Null (wenn der Bruchteil 0 ist, wie gerade erklärt)
    • Kleine Zahlen nahe Null (wenn der Bruchteil nicht 0 ist).

    Um beide Anwendungen zu ermöglichen, wird eine andere, sogenannte denormalisierte, Darstellung verwendet:

    (–1)sign × %0.fraction × 2–1022

    Zum Vergleich sind die kleinsten (im Sinne von „am nächsten bei Null“) Zahlen in normalisierter Darstellung:

    (–1)sign × %1.fraction × 2–1022

    Denormalisierte Zahlen sind kleiner, da es keine führende Ziffer 1 gibt.

Umgang mit Rundungsfehlern

JavaScript-Zahlen werden normalerweise als dezimale Fließkommazahlen eingegeben, sind aber intern als binäre Fließkommazahlen dargestellt. Dies führt zu Ungenauigkeit. Um zu verstehen, warum, vergessen wir das interne Speicherformat von JavaScript und betrachten allgemein, welche Brüche gut durch dezimale Fließkommazahlen und welche durch binäre Fließkommazahlen dargestellt werden können. Im Dezimalsystem sind alle Brüche ein Mantisse m geteilt durch eine Potenz von 10:

Es gibt also im Nenner nur Zehnen. Deshalb kann nicht exakt als dezimale Fließkommazahl ausgedrückt werden – es gibt keine Möglichkeit, eine 3 in den Nenner zu bekommen. Binäre Fließkommazahlen haben nur Zweien im Nenner. Untersuchen wir, welche dezimalen Fließkommazahlen gut als binär und welche nicht dargestellt werden können. Wenn es nur Zweien im Nenner gibt, kann die Dezimalzahl dargestellt werden.

  • 0.5dez = = = 0.1bin
  • 0.75dez = = = 0.11bin
  • 0.125dez = = = 0.001bin

Andere Brüche können nicht exakt dargestellt werden, da sie Zahlen außer 2 im Nenner haben (nach Primfaktorzerlegung).

  • 0.1dez = =
  • 0.2dez = =

Man sieht normalerweise nicht, dass JavaScript intern nicht exakt 0.1 speichert. Aber man kann es sichtbar machen, indem man es mit einer ausreichend hohen Potenz von 10 multipliziert.

> 0.1 * Math.pow(10, 24)
1.0000000000000001e+23

Und wenn Sie zwei ungenau dargestellte Zahlen addieren, ist das Ergebnis manchmal ungenau genug, dass die Ungenauigkeit sichtbar wird.

> 0.1 + 0.2
0.30000000000000004

Ein weiteres Beispiel

> 0.1 + 1 - 1
0.10000000000000009

Aufgrund von Rundungsfehlern sollten Sie als beste Praxis keine nicht-ganzen Zahlen direkt vergleichen. Berücksichtigen Sie stattdessen eine Obergrenze für Rundungsfehler. Eine solche Obergrenze wird als Maschinen-Epsilon bezeichnet. Der Standardwert für doppelte Präzision ist 2−53:

var EPSILON = Math.pow(2, -53);
function epsEqu(x, y) {
    return Math.abs(x - y) < EPSILON;
}

epsEqu() stellt korrekte Ergebnisse sicher, wo ein normaler Vergleich unzureichend wäre.

> 0.1 + 0.2 === 0.3
false
> epsEqu(0.1+0.2, 0.3)
true

Ganze Zahlen in JavaScript

Wie bereits erwähnt, gibt es in JavaScript nur Fließkommazahlen. Ganze Zahlen erscheinen intern auf zwei Arten. Erstens speichert die meisten JavaScript-Engines eine ausreichend kleine Zahl ohne Dezimalbruch als ganze Zahl (mit z. B. 31 Bits) und behält diese Darstellung so lange wie möglich bei. Sie müssen auf eine Fließkommadarstellung zurückschalten, wenn der Betrag einer Zahl zu groß wird oder ein Dezimalbruch auftritt.

Zweitens hat die ECMAScript-Spezifikation Ganzzahloperatoren: nämlich alle bitweisen Operatoren. Diese Operatoren konvertieren ihre Operanden in 32-Bit-Integer und geben 32-Bit-Integer zurück. Für die Spezifikation bedeutet Integer nur, dass die Zahlen keinen Dezimalbruch haben, und 32-Bit bedeutet, dass sie sich innerhalb eines bestimmten Bereichs befinden. Für Engines bedeutet 32-Bit-Integer, dass eine tatsächliche Ganzzahl- (Nicht-Fließkomma-) Darstellung normalerweise eingeführt oder beibehalten werden kann.

Bereiche von ganzen Zahlen

Intern sind die folgenden Bereiche von ganzen Zahlen in JavaScript wichtig:

  • Sichere ganze Zahlen (sieheSichere ganze Zahlen), der größte praktisch nutzbare Bereich von ganzen Zahlen, den JavaScript unterstützt.

    • 53 Bits plus ein Vorzeichen, Bereich (−253, 253)
  • Array-Indizes (sieheArray-Indizes)

    • 32 Bits, vorzeichenlos
    • Maximale Länge: 232−1
    • Bereich der Indizes: [0, 232−1) (maximale Länge ausgeschlossen!)
  • Bitweise Operanden (sieheBitweise Operatoren)

    • Vorzeichenloser Rechtsverschiebungsoperator (>>>): 32 Bits, vorzeichenlos, Bereich [0, 232)
    • Alle anderen bitweisen Operatoren: 32 Bits, einschließlich Vorzeichen, Bereich [−231, 231)
  • „Char-Codes“, UTF-16-Codeeinheiten als Zahlen

Ganze Zahlen als Fließkommazahlen darstellen

JavaScript kann nur Ganzzahlwerte bis zu einem Betrag von 53 Bits handhaben (die 52 Bits des Bruchteils plus 1 indirektes Bit, über den Exponenten; siehe Die interne Darstellung von Zahlen für Details).

Die folgende Tabelle erklärt, wie JavaScript 53-Bit-Ganzzahlen als Fließkommazahlen darstellt.

BitsBereichKodierung

1 Bit

0

(SieheDie interne Darstellung von Zahlen.)

1 Bit

1

%1 × 20

2 Bits

2–3

%1.f51 × 21

3 Bits

4–7 = 22–(23−1)

%1.f51f50 × 22

4 Bits

23–(24−1)

%1.f51f50f49 × 23

53 Bits

252–(253−1)

%1.f51⋯f0 × 252

Es gibt keine feste Bitfolge, die die ganze Zahl darstellt. Stattdessen wird die Mantisse %1.f durch den Exponenten verschoben, so dass die führende Ziffer 1 an der richtigen Stelle steht. In gewisser Weise zählt der Exponent die Anzahl der Bruchteile, die aktiv genutzt werden (die verbleibenden Ziffern sind 0). Das bedeutet, dass wir für 2 Bits eine Ziffer des Bruchteils verwenden und für 53 Bits alle Ziffern des Bruchteils verwenden. Zusätzlich können wir 253 als %1.0 × 253 darstellen, aber bei höheren Zahlen bekommen wir Probleme.

BitsBereichKodierung

54 Bits

253–(254−1)

%1.f51⋯f00 × 253

55 Bits

254–(255−1)

%1.f51⋯f000 × 254

Bei 54 Bits ist die niederwertigste Ziffer immer 0, bei 55 Bits sind die beiden niederwertigsten Ziffern immer 0, und so weiter. Das bedeutet, dass wir bei 54 Bits nur jede zweite Zahl darstellen können, bei 55 Bits nur jede vierte Zahl, und so weiter. Zum Beispiel:

> Math.pow(2, 53) - 1  // OK
9007199254740991
> Math.pow(2, 53)  // OK
9007199254740992
> Math.pow(2, 53) + 1  // can't be represented
9007199254740992
> Math.pow(2, 53) + 2  // OK
9007199254740994

Sichere ganze Zahlen

JavaScript kann Ganzzahlen i nur im Bereich −253 < i < 253 sicher darstellen. Dieser Abschnitt untersucht, was das bedeutet und welche Folgen es hat. Er basiert auf einer E-Mail von Mark S. Miller an die es-discuss Mailingliste.

Die Idee einer sicheren Ganzzahl konzentriert sich darauf, wie mathematische Ganzzahlen in JavaScript dargestellt werden. Im Bereich (−253, 253) (untere und obere Grenzen ausgeschlossen) sind JavaScript-Ganzzahlen sicher: es gibt eine Eins-zu-eins-Zuordnung zwischen mathematischen Ganzzahlen und ihren Darstellungen in JavaScript.

Außerhalb dieses Bereichs sind JavaScript-Ganzzahlen unsicher: zwei oder mehr mathematische Ganzzahlen werden als dieselbe JavaScript-Ganzzahl dargestellt. Zum Beispiel kann JavaScript ab 253 nur jede zweite mathematische Ganzzahl darstellen (der vorherige Abschnitt erklärt, warum). Daher ist eine sichere JavaScript-Ganzzahl eine, die eindeutig eine einzelne mathematische Ganzzahl darstellt.

Definitionen in ECMAScript 6

ECMAScript 6 wird die folgenden Konstanten bereitstellen:

Number.MAX_SAFE_INTEGER = Math.pow(2, 53)-1;
Number.MIN_SAFE_INTEGER = -Number.MAX_SAFE_INTEGER;

Es wird auch eine Funktion bereitstellen, um festzustellen, ob eine Ganzzahl sicher ist:

Number.isSafeInteger = function (n) {
    return (typeof n === 'number' &&
        Math.round(n) === n &&
        Number.MIN_SAFE_INTEGER <= n &&
        n <= Number.MAX_SAFE_INTEGER);
}

Für einen gegebenen Wert n prüft diese Funktion zuerst, ob n eine Zahl und eine ganze Zahl ist. Wenn beide Prüfungen erfolgreich sind, ist n sicher, wenn es größer oder gleich MIN_SAFE_INTEGER und kleiner oder gleich MAX_SAFE_INTEGER ist.

Sichere Ergebnisse von arithmetischen Berechnungen

Wie können wir sicherstellen, dass Ergebnisse von arithmetischen Berechnungen korrekt sind? Zum Beispiel ist das folgende Ergebnis eindeutig nicht korrekt:

> 9007199254740990 + 3
9007199254740992

Wir haben zwei sichere Operanden, aber ein unsicheres Ergebnis.

> Number.isSafeInteger(9007199254740990)
true
> Number.isSafeInteger(3)
true
> Number.isSafeInteger(9007199254740992)
false

Das folgende Ergebnis ist ebenfalls falsch.

> 9007199254740995 - 10
9007199254740986

Diesmal ist das Ergebnis sicher, aber einer der Operanden ist es nicht.

> Number.isSafeInteger(9007199254740995)
false
> Number.isSafeInteger(10)
true
> Number.isSafeInteger(9007199254740986)
true

Daher ist das Ergebnis der Anwendung eines Ganzzahloperators op nur dann garantiert korrekt, wenn alle Operanden und das Ergebnis sicher sind. Formaler:

isSafeInteger(a) && isSafeInteger(b) && isSafeInteger(a op b)

impliziert, dass a op b ein korrektes Ergebnis ist.

Konvertierung in eine ganze Zahl

In JavaScript sind alle Zahlen Fließkommazahlen. Ganze Zahlen sind Fließkommazahlen ohne Bruch. Die Konvertierung einer Zahl n in eine ganze Zahl bedeutet, die „nächste“ ganze Zahl zu n zu finden (wobei die Bedeutung von „nächste“ davon abhängt, wie man konvertiert). Sie haben mehrere Optionen, diese Konvertierung durchzuführen:

  1. Die Math-Funktionen Math.floor(), Math.ceil() und Math.round() (sieheGanze Zahlen über Math.floor(), Math.ceil() und Math.round())
  2. Die benutzerdefinierte Funktion ToInteger() (sieheGanze Zahlen über die benutzerdefinierte Funktion ToInteger())
  3. Bitweise Operatoren (siehe32-Bit-Ganzzahlen über bitweise Operatoren)
  4. Die globale Funktion parseInt() (sieheGanze Zahlen über parseInt())

Spoiler: #1 ist normalerweise die beste Wahl, #2 und #3 haben Nischenanwendungen, und #4 ist zum Parsen von Zeichenketten in Ordnung, aber nicht zum Konvertieren von Zahlen in ganze Zahlen.

Ganze Zahlen über Math.floor(), Math.ceil() und Math.round()

Die folgenden drei Funktionen sind normalerweise die beste Methode, um eine Zahl in eine ganze Zahl umzuwandeln.

  • Math.floor() konvertiert sein Argument in die nächstkleinere ganze Zahl:

    > Math.floor(3.8)
    3
    > Math.floor(-3.8)
    -4
  • Math.ceil() konvertiert sein Argument in die nächstgrößere ganze Zahl:

    > Math.ceil(3.2)
    4
    > Math.ceil(-3.2)
    -3
  • Math.round() konvertiert sein Argument in die nächstgelegene ganze Zahl:

    > Math.round(3.2)
    3
    > Math.round(3.5)
    4
    > Math.round(3.8)
    4

    Das Ergebnis des Rundens von -3.5 kann überraschend sein.

    > Math.round(-3.2)
    -3
    > Math.round(-3.5)
    -3
    > Math.round(-3.8)
    -4

    Daher ist Math.round(x) dasselbe wie

    Math.floor(x + 0.5)

Ganze Zahlen über die benutzerdefinierte Funktion ToInteger()

Eine weitere gute Option, um einen beliebigen Wert in eine ganze Zahl umzuwandeln, ist die interne ECMAScript-Operation ToInteger(), die den Bruch einer Fließkommazahl entfernt. Wenn sie in JavaScript zugänglich wäre, würde sie so funktionieren:

> ToInteger(3.2)
3
> ToInteger(3.5)
3
> ToInteger(3.8)
3
> ToInteger(-3.2)
-3
> ToInteger(-3.5)
-3
> ToInteger(-3.8)
-3

Die ECMAScript-Spezifikation definiert das Ergebnis von ToInteger(number) als

sign(number) × floor(abs(number))

Für das, was sie tut, ist diese Formel relativ kompliziert, da floor die nächstgrößere ganze Zahl sucht; wenn Sie den Bruch einer negativen ganzen Zahl entfernen möchten, müssen Sie die nächstkleinere ganze Zahl suchen. Der folgende Code implementiert die Operation in JavaScript. Wir vermeiden die sign-Operation, indem wir ceil verwenden, wenn die Zahl negativ ist.

function ToInteger(x) {
    x = Number(x);
    return x < 0 ? Math.ceil(x) : Math.floor(x);
}

Bitweise Operatoren (sieheBinäre bitweise Operatoren) konvertieren (mindestens) einen ihrer Operanden in eine 32-Bit-Ganzzahl, die dann manipuliert wird, um ein Ergebnis zu erzeugen, das ebenfalls eine 32-Bit-Ganzzahl ist. Wenn Sie den anderen Operanden entsprechend wählen, erhalten Sie eine schnelle Möglichkeit, eine beliebige Zahl in eine 32-Bit-Ganzzahl (entweder vorzeichenbehaftet oder vorzeichenlos) umzuwandeln.

Bitweises ODER (|)

Wenn die Maske, der zweite Operand, 0 ist, ändern Sie keine Bits und das Ergebnis ist der erste Operand, umgewandelt in eine vorzeichenbehaftete 32-Bit-Ganzzahl. Dies ist die kanonische Methode, diese Art von Umwandlung durchzuführen, und wird z. B. von asm.js verwendet (siehe Ist JavaScript schnell genug?)

// Convert x to a signed 32-bit integer
function ToInt32(x) {
    return x | 0;
}

ToInt32() entfernt den Bruch und wendet Modulo 232 an:

> ToInt32(1.001)
1
> ToInt32(1.999)
1
> ToInt32(1)
1
> ToInt32(-1)
-1
> ToInt32(Math.pow(2, 32)+1)
1
> ToInt32(Math.pow(2, 32)-1)
-1

Schiebeoperatoren

Der gleiche Trick, der für bitweises ODER funktionierte, funktioniert auch für Schiebeoperatoren: wenn Sie um null Bits verschieben, ist das Ergebnis einer Schiebeoperation der erste Operand, umgewandelt in eine 32-Bit-Ganzzahl. Hier sind einige Beispiele für die Implementierung von Operationen der ECMAScript-Spezifikation über Schiebeoperatoren:

// Convert x to a signed 32-bit integer
function ToInt32(x) {
    return x << 0;
}

// Convert x to a signed 32-bit integer
function ToInt32(x) {
    return x >> 0;
}

// Convert x to an unsigned 32-bit integer
function ToUint32(x) {
    return x >>> 0;
}

Hier ist ToUint32() in Aktion:

> ToUint32(-1)
4294967295
> ToUint32(Math.pow(2, 32)-1)
4294967295
> ToUint32(Math.pow(2, 32))
0

Ganze Zahlen über parseInt()

Die Funktion parseInt():

parseInt(str, radix?)

parst die Zeichenkette str (Nicht-Zeichenketten werden umgewandelt) als ganze Zahl. Die Funktion ignoriert führende Leerzeichen und berücksichtigt so viele aufeinanderfolgende gültige Ziffern, wie sie finden kann.

Die Basis

Der Wertebereich für die Radix ist 2 ≤ radix ≤ 36. Sie bestimmt die Basis der zu parsierenden Zahl. Wenn die Radix größer als 10 ist, werden zusätzlich zu 0–9 Buchstaben als Ziffern verwendet (Groß- und Kleinschreibung wird nicht beachtet).

Wenn radix fehlt, wird angenommen, dass sie 10 ist, es sei denn, str beginnt mit „0x“ oder „0X“, in welchem Fall radix auf 16 (hexadezimal) gesetzt wird.

> parseInt('0xA')
10

Wenn radix bereits 16 ist, ist das hexadezimale Präfix optional.

> parseInt('0xA', 16)
10
> parseInt('A', 16)
10

Bisher habe ich das Verhalten von parseInt() gemäß der ECMAScript-Spezifikation beschrieben. Zusätzlich setzen einige Engines die Radix auf 8, wenn str mit einer Null beginnt.

> parseInt('010')
8
> parseInt('0109')  // ignores digits ≥ 8
8

Daher ist es am besten, die Radix immer explizit anzugeben und parseInt() immer mit zwei Argumenten aufzurufen.

Hier sind einige Beispiele.

> parseInt('')
NaN
> parseInt('zz', 36)
1295
> parseInt('   81', 10)
81

> parseInt('12**', 10)
12
> parseInt('12.34', 10)
12
> parseInt(12.34, 10)
12

Verwenden Sie parseInt() nicht, um eine Zahl in einen Integer umzuwandeln. Das letzte Beispiel gibt uns Hoffnung, dass wir parseInt() für die Konvertierung von Zahlen in Integer verwenden können. Leider gibt es hier ein Beispiel, bei dem die Konvertierung falsch ist:

> parseInt(1000000000000000000000.5, 10)
1

Erläuterung

Das Argument wird zuerst in einen String konvertiert.

> String(1000000000000000000000.5)
'1e+21'

parseInt betrachtet „e“ nicht als Integer-Ziffer und stoppt daher das Parsen nach der 1. Hier ist ein weiteres Beispiel:

> parseInt(0.0000008, 10)
8
> String(0.0000008)
'8e-7'

Zusammenfassung

parseInt() sollte nicht verwendet werden, um Zahlen in Integer umzuwandeln: Die Typumwandlung in einen String ist ein unnötiger Umweg und selbst dann ist das Ergebnis nicht immer korrekt.

parseInt() ist nützlich für das Parsen von Strings, aber man muss sich bewusst sein, dass es beim ersten illegalen Zeichen stoppt. Das Parsen von Strings über Number() (siehe Die Funktion Number) ist weniger nachsichtig, kann aber zu Nicht-Integer-Ergebnissen führen.

Arithmetische Operatoren

Die folgenden Operatoren sind für Zahlen verfügbar:

number1 + number2

Numerische Addition, es sei denn, einer der Operanden ist ein String. Dann werden beide Operanden in Strings konvertiert und verkettet (siehe Der Plus-Operator (+))

> 3.1 + 4.3
7.4
> 4 + ' messages'
'4 messages'
number1 - number2
Subtraktion.
number1 * number2
Multiplikation.
number1 / number2
Division.
number1 % number2

Rest

> 9 % 7
2
> -9 % 7
-2

Warnung

Diese Operation ist keine Modulo-Operation. Sie gibt einen Wert zurück, dessen Vorzeichen mit dem des ersten Operanden übereinstimmt (mehr dazu gleich).

-number
Negiert seinen Operanden.
+number
Belässt seinen Operanden unverändert; Nicht-Zahlen werden in eine Zahl konvertiert.
++variable, --variable

Gibt den aktuellen Wert der Variablen zurück, nachdem sie um 1 erhöht (oder verringert) wurde:

> var x = 3;
> ++x
4
> x
4
variable++, variable--

Erhöht (oder verringert) den Wert der Variablen um 1 und gibt ihn zurück.

> var x = 3;
> x++
3
> x
4

Mnemotechnik: Inkrement- (++) und Dekrement-Operatoren (--)

Die Position des Operanden kann Ihnen helfen zu erinnern, ob er vor oder nach der Erhöhung (oder Verringerung) zurückgegeben wird. Wenn der Operand vor dem Inkrement-Operator steht, wird er vor der Erhöhung zurückgegeben. Wenn der Operand nach dem Operator steht, wird er erhöht und dann zurückgegeben. (Der Dekrement-Operator funktioniert ähnlich.)

Bitweise Operatoren

JavaScript hat mehrere bitweise Operatoren, die mit 32-Bit-Integern arbeiten. Das heißt, sie konvertieren ihre Operanden in 32-Bit-Integer und produzieren ein Ergebnis, das ein 32-Bit-Integer ist. Anwendungsfälle für diese Operatoren sind die Verarbeitung binärer Protokolle, spezielle Algorithmen usw.

Hintergrundwissen

Dieser Abschnitt erklärt einige Konzepte, die Ihnen helfen werden, bitweise Operatoren zu verstehen.

Binäre Komplemente

Zwei gängige Methoden zur Berechnung eines binären Komplements (oder Inversen) einer Binärzahl sind:

Einerkomplement

Sie berechnen das Einerkomplement ~x einer Zahl x, indem Sie jede der 32 Ziffern invertieren. Wir veranschaulichen das Einerkomplement anhand von vierstelligen Zahlen. Das Einerkomplement von 1100 ist 0011. Wenn Sie eine Zahl zu ihrem Einerkomplement addieren, erhalten Sie eine Zahl, deren Ziffern alle 1 sind.

1 + ~1 = 0001 + 1110 = 1111
Zweierkomplement

Das Zweierkomplement -x einer Zahl x ist das Einerkomplement plus eins. Wenn Sie eine Zahl zu ihrem Zweierkomplement addieren, erhalten Sie 0 (wenn man Überläufe über die höchstwertige Stelle hinaus ignoriert). Hier ist ein Beispiel mit vierstelligen Zahlen:

1 + -1 = 0001 + 1111 = 0000

Eingabe und Ausgabe binärer Zahlen

In den folgenden Beispielen arbeiten wir mit binären Zahlen über die folgenden beiden Operationen:

Bitweiser Nicht-Operator

~number berechnet das Einerkomplement von number:

> (~parseInt('11111111111111111111111111111111', 2)).toString(2)
'0'

Binäre Bitweise Operatoren

JavaScript hat drei binäre bitweise Operatoren:

  • number1 & number2 (bitwise And):

    > (parseInt('11001010', 2) & parseInt('1111', 2)).toString(2)
    '1010'
  • number1 | number2 (bitwise Or):

    > (parseInt('11001010', 2) | parseInt('1111', 2)).toString(2)
    '11001111'
  • number1 ^ number2 (bitwise Xor; eXclusive Or)

    > (parseInt('11001010', 2) ^ parseInt('1111', 2)).toString(2)
    '11000101'

Es gibt zwei Möglichkeiten, binäre bitweise Operatoren intuitiv zu verstehen:

Eine boolesche Operation pro Bit

In den folgenden Formeln bedeutet ni Bit i von Zahl n, interpretiert als boolescher Wert (0 ist false, 1 ist true). Zum Beispiel ist 20 false; 21 ist true.

  • Und: resulti = number1i && number2i
  • Oder: resulti = number1i || number2i
  • Xor: resulti = number1i ^^ number2i

    Der Operator ^^ existiert nicht. Wenn er existieren würde, würde er so funktionieren (das Ergebnis ist true, wenn genau einer der Operanden true ist).

    x ^^ y === (x && !y) || (!x && y)
Ändern von Bits von number1 über number2
  • Und: Behält nur die Bits von number1, die in number2 gesetzt sind. Diese Operation wird auch als Maskierung bezeichnet, wobei number2 die Maske ist.
  • Oder: Setzt alle Bits von number1, die in number2 gesetzt sind, und behält alle anderen Bits unverändert.
  • Xor: Invertiert alle Bits von number1, die in number2 gesetzt sind, und behält alle anderen Bits unverändert.

Bitweise Verschiebeoperatoren

JavaScript hat drei bitweise Verschiebeoperatoren:

Die Funktion Number

Die Funktion Number kann auf zwei Arten aufgerufen werden:

Number(value)

Als normale Funktion konvertiert sie value in eine primitive Zahl (siehe Konvertierung in Number).

> Number('123')
123
> typeof Number(3)  // no change
'number'
new Number(num)

Als Konstruktor erstellt sie eine neue Instanz von Number (siehe Wrapper-Objekte für Primitives), ein Objekt, das num umschließt (nachdem es in eine Zahl konvertiert wurde). Zum Beispiel:

> typeof new Number(3)
'object'

Der erstere Aufruf ist der gebräuchlichste.

Number-Konstruktor-Eigenschaften

Das Objekt Number hat die folgenden Eigenschaften:

Number.MAX_VALUE

Die größte darstellbare positive Zahl. Intern sind alle Ziffern ihrer Bruchteile eins und der Exponent ist maximal, nämlich 1023. Wenn Sie versuchen, den Exponenten durch Multiplikation mit zwei zu erhöhen, ist das Ergebnis der Fehlerwert Infinity (siehe Infinity)

> Number.MAX_VALUE
1.7976931348623157e+308
> Number.MAX_VALUE * 2
Infinity
Number.MIN_VALUE

Die kleinste darstellbare positive Zahl (größer als Null, ein winziger Bruch):

> Number.MIN_VALUE
5e-324
Number.NaN
Derselbe Wert wie das globale NaN.
Number.NEGATIVE_INFINITY

Derselbe Wert wie -Infinity:

> Number.NEGATIVE_INFINITY === -Infinity
true
Number.POSITIVE_INFINITY

Derselbe Wert wie Infinity:

> Number.POSITIVE_INFINITY === Infinity
true

Number-Prototyp-Methoden

Alle Methoden von primitiven Zahlen sind in Number.prototype gespeichert (siehe Primitives leihen sich ihre Methoden von Wrappern).

Number.prototype.toFixed(fractionDigits?)

Number.prototype.toFixed(fractionDigits?) gibt eine exponen-freie Darstellung der Zahl zurück, gerundet auf fractionDigits Stellen. Wenn der Parameter weggelassen wird, wird der Wert 0 verwendet:

> 0.0000003.toFixed(10)
'0.0000003000'
> 0.0000003.toString()
'3e-7'

Wenn die Zahl größer oder gleich 1021 ist, dann funktioniert diese Methode genauso wie toString(). Sie erhalten eine Zahl in exponentieller Notation.

> 1234567890123456789012..toFixed()
'1.2345678901234568e+21'
> 1234567890123456789012..toString()
'1.2345678901234568e+21'

Number.prototype.toPrecision(precision?)

Number.prototype.toPrecision(precision?) kürzt die Mantisse auf precision Ziffern, bevor ein Konvertierungsalgorithmus ähnlich wie toString() verwendet wird. Wenn keine Präzision angegeben ist, wird toString() direkt verwendet:

> 1234..toPrecision(3)
'1.23e+3'

> 1234..toPrecision(4)
'1234'

> 1234..toPrecision(5)
'1234.0'

> 1.234.toPrecision(3)
'1.23'

Sie benötigen die Exponentialnotation, um 1234 mit einer Präzision von drei Stellen anzuzeigen.

Number.prototype.toString(radix?)

Für Number.prototype.toString(radix?) gibt der Parameter radix die Basis des Systems an, in dem die Zahl angezeigt werden soll. Die gängigsten Radices sind 10 (dezimal), 2 (binär) und 16 (hexadezimal):

> 15..toString(2)
'1111'
> 65535..toString(16)
'ffff'

Die Radix muss mindestens 2 und höchstens 36 sein. Jede Radix größer als 10 führt zur Verwendung von alphabetischen Zeichen als Ziffern, was das Maximum von 36 erklärt, da das lateinische Alphabet 26 Zeichen hat:

> 1234567890..toString(36)
'kf12oi'

Die globale Funktion parseInt (siehe Integer über parseInt()) ermöglicht es Ihnen, solche Notationen zurück in eine Zahl zu konvertieren:

> parseInt('kf12oi', 36)
1234567890

Dezimal-Exponentialnotation

Für die Radix 10 verwendet toString() in zwei Fällen eine Exponentialnotation (mit einer einzelnen Ziffer vor dem Dezimalpunkt). Erstens, wenn mehr als 21 Ziffern vor dem Dezimalpunkt einer Zahl vorhanden sind.

> 1234567890123456789012
1.2345678901234568e+21
> 123456789012345678901
123456789012345680000

Zweitens, wenn eine Zahl mit 0. beginnt, gefolgt von mehr als fünf Nullen und einer Nicht-Null-Ziffer.

> 0.0000003
3e-7
> 0.000003
0.000003

In allen anderen Fällen wird eine feste Notation verwendet.

Number.prototype.toExponential(fractionDigits?)

Number.prototype.toExponential(fractionDigits?) erzwingt die Ausdrucksweise einer Zahl in Exponentialnotation. fractionDigits ist eine Zahl zwischen 0 und 20, die bestimmt, wie viele Ziffern nach dem Dezimalpunkt angezeigt werden sollen. Wenn sie weggelassen wird, werden so viele signifikante Ziffern wie nötig verwendet, um die Zahl eindeutig zu spezifizieren.

In diesem Beispiel erzwingen wir mehr Präzision, wenn toString() ebenfalls die Exponentialnotation verwenden würde. Die Ergebnisse sind gemischt, da wir die Grenzen der Präzision erreichen, die bei der Konvertierung von Binärzahlen in eine Dezimalnotation erzielt werden kann.

> 1234567890123456789012..toString()
'1.2345678901234568e+21'

> 1234567890123456789012..toExponential(20)
'1.23456789012345677414e+21'

In diesem Beispiel ist die Größenordnung der Zahl nicht groß genug für einen Exponenten, der von toString() angezeigt wird. toExponential() zeigt jedoch einen Exponenten an.

> 1234..toString()
'1234'

> 1234..toExponential(5)
'1.23400e+3'

> 1234..toExponential()
'1.234e+3'

In diesem Beispiel erhalten wir Exponentialnotation, wenn der Bruch nicht klein genug ist.

> 0.003.toString()
'0.003'

> 0.003.toExponential(4)
'3.0000e-3'

> 0.003.toExponential()
'3e-3'

Funktionen für Zahlen

Die folgenden Funktionen arbeiten mit Zahlen:

isFinite(number)
Prüft, ob number eine tatsächliche Zahl ist (weder Infinity noch NaN). Details finden Sie unter Prüfung auf Infinity.
isNaN(number)
Gibt true zurück, wenn number NaN ist. Details finden Sie unter Fallstrick: Prüfung, ob ein Wert NaN ist.
parseFloat(str)
Wandelt str in eine Fließkommazahl um. Details finden Sie unter parseFloat().
parseInt(str, radix?)
Parst str als Integer, dessen Basis radix (2–36) ist. Details finden Sie unter Integer über parseInt().

Quellen für dieses Kapitel

Ich habe die folgenden Quellen bei der Erstellung dieses Kapitels herangezogen:



[14] Quelle: Brendan Eich, http://bit.ly/1lKzQeC.

[15] Béla Varga (@netzzwerg) wies darauf hin, dass IEEE 754 NaN als ungleich sich selbst spezifiziert.

Nächstes: 12. Strings