In modernem JavaScript gibt es zwei Arten von Zahlen:
-
Regelmäßige Zahlen in JavaScript werden im 64-Bit-Format IEEE-754 gespeichert, auch bekannt als „double precision floating point numbers“. Das sind die Zahlen, die wir die meiste Zeit verwenden und über die wir in diesem Kapitel sprechen werden.
-
BigInt-Zahlen, um ganze Zahlen beliebiger Länge darzustellen. Sie werden manchmal benötigt, weil eine normale Zahl nicht größer als
253
oder kleiner als-253
sein kann. Da Bigints in wenigen Spezialgebieten verwendet werden, widmen wir ihnen ein eigenes Kapitel BigInt.
So werden wir hier über reguläre Zahlen sprechen. Lassen Sie uns unser Wissen über sie erweitern.
Mehr Möglichkeiten, eine Zahl zu schreiben
Stellen Sie sich vor, wir müssen 1 Milliarde schreiben. Die offensichtliche Art ist:
let billion = 1000000000;
Wir können auch den Unterstrich _
als Trennzeichen verwenden:
let billion = 1_000_000_000;
Hier spielt der Unterstrich _
die Rolle des „syntaktischen Zuckers“, er macht die Zahl besser lesbar. Die JavaScript-Engine ignoriert einfach _
zwischen den Ziffern, also ist es genau die gleiche eine Milliarde wie oben.
Im wirklichen Leben versuchen wir jedoch zu vermeiden, lange Folgen von Nullen zu schreiben. Dafür sind wir zu faul. Wir werden versuchen, etwas wie "1bn"
für eine Milliarde oder "7.3bn"
für 7 Milliarden 300 Millionen zu schreiben. Das Gleiche gilt für die meisten großen Zahlen.
In JavaScript können wir eine Zahl kürzen, indem wir den Buchstaben "e"
an sie anhängen und die Anzahl der Nullen angeben:
let billion = 1e9; // 1 billion, literally: 1 and 9 zeroesalert( 7.3e9 ); // 7.3 billions (same as 7300000000 or 7_300_000_000)
e
die Zahl mit 1
mit der angegebenen Anzahl der Nullen.
1e3 = 1 * 1000 // e3 means *10001.23e6 = 1.23 * 1000000 // e6 means *1000000
Nun wollen wir etwas sehr Kleines schreiben. Sagen wir, 1 Mikrosekunde (ein Millionstel einer Sekunde):
let ms = 0.000001;
Genauso wie zuvor, kann die Verwendung von "e"
helfen. Wenn wir es vermeiden möchten, die Nullen explizit zu schreiben, könnten wir dasselbe sagen:
let ms = 1e-6; // six zeroes to the left from 1
Wenn wir die Nullen in 0.000001
zählen, sind es 6. Also ist es natürlich 1e-6
.
In anderen Worten, eine negative Zahl nach "e"
bedeutet eine Division durch 1 mit der angegebenen Anzahl von Nullen:
// -3 divides by 1 with 3 zeroes1e-3 = 1 / 1000 (=0.001)// -6 divides by 1 with 6 zeroes1.23e-6 = 1.23 / 1000000 (=0.00000123)
Hex-, Binär- und Oktalzahlen
Hexadezimale Zahlen werden in JavaScript häufig verwendet, um Farben darzustellen, Zeichen zu kodieren und für viele andere Dinge. Daher gibt es natürlich eine kürzere Art, sie zu schreiben: 0x
und dann die Zahl.
Zum Beispiel:
alert( 0xff ); // 255alert( 0xFF ); // 255 (the same, case doesn't matter)
Binäre und oktale Zahlensysteme werden nur selten verwendet, aber auch mit den Präfixen 0b
und 0o
unterstützt:
let a; // binary form of 255let b = 0o377; // octal form of 255alert( a == b ); // true, the same number 255 at both sides
Es gibt nur 3 Zahlensysteme mit solcher Unterstützung. Für andere Zahlensysteme sollten wir die Funktion parseInt
verwenden (die wir später in diesem Kapitel sehen werden).
toString(base)
Die Methode num.toString(base)
gibt eine String-Darstellung von num
im Zahlensystem mit dem angegebenen base
zurück.
Beispielsweise:
let num = 255;alert( num.toString(16) ); // ffalert( num.toString(2) ); // 11111111
Das base
kann von 2
bis 36
variieren. Standardmäßig ist es 10
.
Gängige Anwendungsfälle hierfür sind:
-
base=16 wird für Hex-Farben, Zeichenkodierungen usw. verwendet, die Ziffern können
0..9
oderA..F
sein. -
Base=2 ist meist zum Debuggen von bitweisen Operationen, die Ziffern können
0
oder1
sein. -
Basis=36 ist das Maximum, Ziffern können
0..9
oderA..Z
sein. Das gesamte lateinische Alphabet wird verwendet, um eine Zahl darzustellen. Ein lustiger, aber nützlicher Fall für36
ist, wenn wir einen langen numerischen Bezeichner in etwas Kürzeres umwandeln müssen, zum Beispiel um eine kurze URL zu erstellen. Man kann es einfach im Zahlensystem mit base36
darstellen:alert( 123456..toString(36) ); // 2n9c
Bitte beachten Sie, dass zwei Punkte in 123456..toString(36)
kein Tippfehler sind. Wenn wir eine Methode direkt auf einer Zahl aufrufen wollen, wie toString
im obigen Beispiel, dann müssen wir zwei Punkte ..
dahinter setzen.
Wenn wir einen einzelnen Punkt setzen: 123456.toString(36)
, dann gäbe es einen Fehler, denn die JavaScript-Syntax impliziert den Dezimalteil nach dem ersten Punkt. Und wenn wir einen weiteren Punkt setzen, dann weiß JavaScript, dass der Dezimalteil leer ist und jetzt geht die Methode.
Auch könnte man (123456).toString(36)
schreiben.
Runden
Eine der am häufigsten verwendeten Operationen beim Arbeiten mit Zahlen ist das Runden.
Es gibt mehrere eingebaute Funktionen zum Runden:
Math.floor
Rundet ab: 3.1
wird zu 3
, und -1.1
wird zu -2
Math.ceil
Rundet auf: 3.1
wird zu 4
, und -1.1
wird zu -1
Math.round
Rundet auf die nächste ganze Zahl: 3.1
wird zu 3
3.6
wird zu 4
, der mittlere Fall: 3.5
rundet ebenfalls zu 4
auf. Math.trunc
(wird vom Internet Explorer nicht unterstützt) Entfernt alles nach dem Dezimalpunkt, ohne zu runden: 3.1
wird zu 3
-1.1
wird zu -1
.
Hier ist die Tabelle, die die Unterschiede zwischen ihnen zusammenfasst:
Math.floor |
Math.ceil |
Math.round |
Math.trunc |
|
---|---|---|---|---|
3.1 |
3 |
4 |
3 |
3 |
3.6 |
3 |
4 |
4 |
3 |
-1.1 |
-2 |
-1 |
-1 |
-1 |
-1.6 |
-2 |
-1 |
-2 |
-1 |
Diese Funktionen decken alle möglichen Arten ab, wie man mit dem Dezimalteil einer Zahl umgehen kann. Aber was ist, wenn wir die Zahl auf die n-th
Stelle nach dem Komma runden möchten?
Zum Beispiel haben wir 1.2345
und wollen sie auf 2 Stellen runden, wobei wir nur 1.23
erhalten.
Es gibt zwei Möglichkeiten, dies zu tun:
-
Multiplizieren und dividieren.
Um die Zahl auf die 2. Nachkommastelle zu runden, können wir zum Beispiel die Zahl mit
100
(oder einer größeren Potenz von 10) multiplizieren, die Rundungsfunktion aufrufen und dann wieder dividieren.let num = 1.23456;alert( Math.round(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23
Die Methode toFixed(n) rundet die Zahl auf
n
Nachkommastellen und liefert eine String-Darstellung des Ergebnisses.let num = 12.34;alert( num.toFixed(1) ); // "12.3"
Dies rundet auf oder ab auf den nächsten Wert, ähnlich wie
Math.round
:let num = 12.36;alert( num.toFixed(1) ); // "12.4"
Bitte beachten Sie, dass das Ergebnis von
toFixed
ein String ist. Wenn der Dezimalteil kürzer als erforderlich ist, werden Nullen am Ende angehängt:let num = 12.34;alert( num.toFixed(5) ); // "12.34000", added zeroes to make exactly 5 digits
Wir können es mit dem unären Plus oder einem
Number()
-Aufruf in eine Zahl umwandeln:+num.toFixed(5)
.Ungenaue Berechnungen
Intern wird eine Zahl im 64-Bit-Format IEEE-754 dargestellt, es gibt also genau 64 Bits, um eine Zahl zu speichern: 52 davon werden verwendet, um die Ziffern zu speichern, 11 davon speichern die Position des Dezimalpunkts (bei ganzen Zahlen sind sie Null), und 1 Bit ist für das Vorzeichen.
Wenn eine Zahl zu groß ist, würde sie den 64-Bit-Speicher überlaufen lassen und möglicherweise eine Unendlichkeit ergeben:
alert( 1e500 ); // Infinity
Was vielleicht etwas weniger offensichtlich ist, aber recht häufig vorkommt, ist der Verlust der Präzision.
Betrachten Sie diesen (verfälschten!) Test:
alert( 0.1 + 0.2 == 0.3 ); // false
Das ist richtig, wenn wir prüfen, ob die Summe von
0.1
und0.2
0.3
ist, erhalten wirfalse
.Strange! Was ist es dann, wenn nicht
0.3
?alert( 0.1 + 0.2 ); // 0.30000000000000004
Autsch! Hier gibt es mehr Konsequenzen als einen falschen Vergleich. Stellen Sie sich vor, Sie machen eine E-Shopping-Seite und der Besucher legt
$0.10
und$0.20
Waren in seinen Warenkorb. Die Bestellsumme beträgt$0.30000000000000004
. Das würde jeden überraschen.Aber warum passiert das?
Eine Zahl wird im Speicher in ihrer binären Form gespeichert, einer Folge von Bits – Einsen und Nullen. Aber Brüche wie
0.1
0.2
, die im dezimalen Zahlensystem einfach aussehen, sind in ihrer binären Form eigentlich unendliche Brüche.In anderen Worten, was ist
0.1
? Es ist eins geteilt durch zehn1/10
, ein Zehntel. Im dezimalen Zahlensystem sind solche Zahlen leicht darstellbar. Vergleichen Sie es mit einem Drittel:1/3
. Es wird ein unendlicher Bruch0.33333(3)
.So funktioniert die Division durch Potenzen
10
im Dezimalsystem garantiert gut, die Division durch3
aber nicht. Aus dem gleichen Grund funktioniert im binären Zahlensystem die Division durch Potenzen von2
garantiert, aber1/10
wird zu einem endlosen binären Bruch.Es gibt einfach keine Möglichkeit, genau 0.1 oder genau 0,2 im Binärsystem zu speichern, genauso wie es keine Möglichkeit gibt, ein Drittel als Dezimalbruch zu speichern.
Das Zahlenformat IEEE-754 löst dies durch Rundung auf die nächstmögliche Zahl. Diese Rundungsregeln erlauben es uns normalerweise nicht, diesen „winzigen Präzisionsverlust“ zu sehen, aber er existiert.
Wir können das in Aktion sehen:
alert( 0.1.toFixed(20) ); // 0.10000000000000000555
Und wenn wir zwei Zahlen addieren, addieren sich ihre „Präzisionsverluste“.
Deshalb ist 0.1 + 0.2
nicht gerade 0.3
.
Das gleiche Problem gibt es in vielen anderen Programmiersprachen.
PHP, Java, C, Perl, Ruby liefern genau das gleiche Ergebnis, da sie auf dem gleichen Zahlenformat basieren.
Kann man das Problem umgehen? Sicher, die zuverlässigste Methode ist, das Ergebnis mit Hilfe einer Methode toFixed(n) zu runden:
let sum = 0.1 + 0.2;alert( sum.toFixed(2) ); // 0.30
Bitte beachten Sie, dass toFixed
immer einen String zurückgibt. Er stellt sicher, dass er 2 Ziffern nach dem Dezimalpunkt hat. Das ist eigentlich praktisch, wenn wir ein E-Shopping haben und $0.30
anzeigen müssen. Für andere Fälle können wir das unäre Plus verwenden, um es in eine Zahl zu zwingen:
let sum = 0.1 + 0.2;alert( +sum.toFixed(2) ); // 0.3
Wir können die Zahlen auch vorübergehend mit 100 (oder einer größeren Zahl) multiplizieren, um sie in ganze Zahlen zu verwandeln, die Mathematik zu machen und dann wieder zu dividieren. Da wir dann mit ganzen Zahlen rechnen, verringert sich der Fehler etwas, aber wir bekommen ihn immer noch bei der Division:
alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001
Der Ansatz „Multiplizieren/Teilen“ verringert also den Fehler, beseitigt ihn aber nicht vollständig.
Manchmal können wir versuchen, Brüche überhaupt zu vermeiden. Wenn es sich z. B. um ein Geschäft handelt, dann können wir die Preise in Cent statt in Dollar speichern. Aber was ist, wenn wir einen Rabatt von 30 % anwenden? In der Praxis ist es selten möglich, Brüche vollständig zu umgehen. Wir runden einfach ab, um bei Bedarf „Schwänze“ abzuschneiden.
Versuchen Sie Folgendes:
// Hello! I'm a self-increasing number!alert( 9999999999999999 ); // shows 10000000000000000
Dieses leidet unter dem gleichen Problem: einem Verlust an Präzision. Es gibt 64 Bits für die Zahl, 52 davon können zum Speichern von Ziffern verwendet werden, aber das ist nicht genug. Also verschwinden die niederwertigsten Ziffern.
JavaScript löst in solchen Fällen keinen Fehler aus. Es tut sein Bestes, um die Zahl in das gewünschte Format einzupassen, aber leider ist dieses Format nicht groß genug.
Eine weitere lustige Folge der internen Darstellung von Zahlen ist die Existenz von zwei Nullen: 0
und -0
.
Das liegt daran, dass ein Vorzeichen durch ein einzelnes Bit repräsentiert wird, so dass es für jede Zahl, einschließlich einer Null, gesetzt oder nicht gesetzt sein kann.
In den meisten Fällen ist die Unterscheidung unmerklich, weil die Operatoren geeignet sind, sie als das Gleiche zu behandeln.
Tests: isFinite und isNaN
Erinnern Sie sich an diese beiden speziellen numerischen Werte?
-
Infinity
(und-Infinity
) ist ein spezieller numerischer Wert, der größer (kleiner) als irgendetwas ist. -
NaN
stellt einen Fehler dar.
Sie gehören zum Typ number
, sind aber keine „normalen“ Zahlen, daher gibt es spezielle Funktionen, um sie zu prüfen:
-
isNaN(value)
wandelt sein Argument in eine Zahl um und prüft dann, ob esNaN
ist:alert( isNaN(NaN) ); // truealert( isNaN("str") ); // true
Aber brauchen wir diese Funktion? Können wir nicht einfach den Vergleich === NaN
verwenden? Die Antwort ist leider nein. Der Wert NaN
ist insofern einzigartig, als er mit nichts gleich ist, auch nicht mit sich selbst:
alert( NaN === NaN ); // false
isFinite(value)
wandelt sein Argument in eine Zahl um und gibt true
zurück, wenn es eine reguläre Zahl ist, nicht NaN/Infinity/-Infinity
:
alert( isFinite("15") ); // truealert( isFinite("str") ); // false, because a special value: NaNalert( isFinite(Infinity) ); // false, because a special value: Infinity
Manchmal wird isFinite
verwendet, um zu überprüfen, ob ein String-Wert eine reguläre Zahl ist:
let num = +prompt("Enter a number", '');// will be true unless you enter Infinity, -Infinity or not a numberalert( isFinite(num) );
Bitte beachten Sie, dass ein leerer oder ein mit Leerzeichennur eine leere Zeichenkette als 0
in allen numerischen Funktionen einschließlich isFinite
behandelt wird.
Object.is
Es gibt eine spezielle eingebaute Methode Object.is
, die Werte wie ===
vergleicht, aber für zwei Randfälle zuverlässiger ist:
- Es funktioniert mit
NaN
Object.is(NaN, NaN) === true
, das ist eine gute Sache. - Die Werte
0
und-0
sind unterschiedlich:Object.is(0, -0) === false
, technisch gesehen stimmt das, denn intern hat die Zahl ein Vorzeichenbit, das unterschiedlich sein kann, auch wenn alle anderen Bits Nullen sind.
In allen anderen Fällen ist Object.is(a, b)
dasselbe wie a === b
.
Diese Art des Vergleichs wird oft in JavaScript-Spezifikationen verwendet. Wenn ein interner Algorithmus zwei Werte daraufhin vergleichen muss, ob sie genau gleich sind, verwendet er Object.is
(intern SameValue genannt).
parseInt und parseFloat
Die numerische Konvertierung mit einem Plus +
oder Number()
ist streng. Wenn ein Wert nicht genau eine Zahl ist, schlägt er fehl:
alert( +"100px" ); // NaN
Die einzige Ausnahme sind Leerzeichen am Anfang oder am Ende der Zeichenkette, da sie ignoriert werden.
Aber im realen Leben haben wir oft Werte in Einheiten, wie "100px"
oder "12pt"
in CSS. Außerdem steht in vielen Ländern das Währungssymbol hinter dem Betrag, so dass wir "19€"
haben und daraus einen numerischen Wert extrahieren möchten.
Dafür sind parseInt
und parseFloat
da.
Sie „lesen“ eine Zahl aus einem String, bis sie nicht mehr können. Im Falle eines Fehlers wird die gesammelte Zahl zurückgegeben. Die Funktion parseInt
liefert eine Ganzzahl, während parseFloat
eine Fließkommazahl zurückgibt:
alert( parseInt('100px') ); // 100alert( parseFloat('12.5em') ); // 12.5alert( parseInt('12.3') ); // 12, only the integer part is returnedalert( parseFloat('12.3.4') ); // 12.3, the second point stops the reading
Es gibt Situationen, in denen parseInt/parseFloat
NaN
zurückgibt. Das passiert, wenn keine Ziffern gelesen werden konnten:
alert( parseInt('a123') ); // NaN, the first symbol stops the process
parseInt(str, radix)
Die Funktion parseInt()
hat einen optionalen zweiten Parameter. Er gibt die Basis des Zahlensystems an, so dass parseInt
auch Zeichenketten mit Hexadezimalzahlen, Binärzahlen und so weiter parsen kann:
alert( parseInt('0xff', 16) ); // 255alert( parseInt('ff', 16) ); // 255, without 0x also worksalert( parseInt('2n9c', 36) ); // 123456
Weitere mathematische Funktionen
JavaScript hat ein eingebautes Math-Objekt, das eine kleine Bibliothek von mathematischen Funktionen und Konstanten enthält.
Ein paar Beispiele:
Math.random()
Gibt eine Zufallszahl von 0 bis 1 zurück (1 nicht eingeschlossen).
alert( Math.random() ); // 0.1234567894322alert( Math.random() ); // 0.5435252343232alert( Math.random() ); // ... (any random numbers)
Math.max(a, b, c...)
Math.min(a, b, c...)
Gibt das größte/kleinste aus einer beliebigen Anzahl von Argumenten zurück.
alert( Math.max(3, 5, -10, 0, 1) ); // 5alert( Math.min(1, 2) ); // 1
Math.pow(n, power)
Returns n
raised to the given power.
alert( Math.pow(2, 10) ); // 2 in power 10 = 1024
Es gibt noch weitere Funktionen und Konstanten im Math
Objekt, darunter auch die Trigonometrie, die Sie in den Docs zum Math-Objekt finden können.
Zusammenfassung
Um Zahlen mit vielen Nullen zu schreiben:
- Hängen Sie
"e"
mit der Anzahl der Nullen an die Zahl an. Beispiel:123e6
ist dasselbe wie123
mit 6 Nullen123000000
. - Eine negative Zahl nach
"e"
bewirkt, dass die Zahl mit gegebenen Nullen durch 1 geteilt wird. Z.B.123e-6
bedeutet0.000123
123
Millionstel).
Für verschiedene Zahlensysteme:
- Kann Zahlen direkt in Hex- (
0x
), Oktal- (0o
) und Binärsystemen (0b
) schreiben. -
parseInt(str, base)
parst die Zeichenkettestr
in eine Ganzzahl im Ziffernsystem mit gegebenembase
2 ≤ base ≤ 36
. -
num.toString(base)
konvertiert eine Zahl in eine Zeichenkette im Zahlensystem mit dem angegebenenbase
.
Für die Konvertierung von Werten wie 12pt
und 100px
in eine Zahl:
- Verwenden Sie
parseInt/parseFloat
für die „weiche“ Konvertierung, die eine Zahl aus einem String liest und dann den Wert zurückgibt, den sie vor dem Fehler lesen konnten.
Für Brüche:
- Runden Sie mit
Math.floor
Math.ceil
Math.trunc
Math.round
odernum.toFixed(precision)
. - Denken Sie daran, dass es einen Präzisionsverlust gibt, wenn Sie mit Brüchen arbeiten.
Weitere mathematische Funktionen:
- Sehen Sie sich das Math-Objekt an, wenn Sie es brauchen. Die Bibliothek ist sehr klein, kann aber grundlegende Bedürfnisse abdecken.