Im modernen JavaScript gibt es zwei Arten von Zahlen:
Reguläre Zahlen werden in JavaScript im 64-Bit-Format IEEE-754 gespeichert, das auch als „Gleitkommazahlen mit doppelter Genauigkeit“ bezeichnet wird. Dies sind Zahlen, die wir am häufigsten verwenden, und wir werden in diesem Kapitel darüber sprechen.
BigInt-Zahlen stellen ganze Zahlen beliebiger Länge dar. Sie werden manchmal benötigt, weil eine reguläre Ganzzahl nicht sicher größer als (2 53 -1)
oder kleiner als -(2 53 -1)
sein kann, wie wir bereits im Kapitel Datentypen erwähnt haben. Da Bigints in einigen Spezialgebieten verwendet werden, widmen wir ihnen ein spezielles Kapitel BigInt.
Hier sprechen wir also über reguläre Zahlen. Erweitern wir unser Wissen darüber.
Stellen Sie sich vor, wir müssten 1 Milliarde schreiben. Der offensichtliche Weg ist:
sei eine Milliarde = 1000000000;
Wir können auch den Unterstrich _
als Trennzeichen verwenden:
sei eine Milliarde = 1_000_000_000;
Dabei übernimmt der Unterstrich _
die Rolle des „syntaktischen Zuckers“, er macht die Zahl besser lesbar. Die JavaScript-Engine ignoriert einfach _
zwischen den Ziffern, es handelt sich also genau um die gleiche Milliarde wie oben.
Im wirklichen Leben versuchen wir jedoch, das Schreiben langer Folgen von Nullen zu vermeiden. Dafür sind wir zu faul. Wir werden versuchen, so 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"
anhängen und die Anzahl der Nullen angeben:
sei Milliarde = 1e9; // 1 Milliarde, wörtlich: 1 und 9 Nullen alarm( 7.3e9 ); // 7,3 Milliarden (entspricht 7300000000 oder 7_300_000_000)
Mit anderen Worten: e
multipliziert die Zahl mit 1
mit der angegebenen Nullenanzahl.
1e3 === 1 * 1000; // e3 bedeutet *1000 1,23e6 === 1,23 * 1000000; // e6 bedeutet *1000000
Jetzt lasst uns etwas ganz Kleines schreiben. Sagen wir, 1 Mikrosekunde (ein Millionstel einer Sekunde):
sei mсs = 0,000001;
Wie zuvor kann die Verwendung "e"
hilfreich sein. Wenn wir das explizite Schreiben der Nullen vermeiden möchten, könnten wir dasselbe schreiben wie:
sei mcs = 1e-6; // fünf Nullen links von 1
Wenn wir die Nullen in 0.000001
zählen, sind es 6 davon. Also ist es natürlich 1e-6
.
Mit anderen Worten bedeutet eine negative Zahl nach "e"
eine Division durch 1 mit der angegebenen Anzahl an Nullen:
// -3 dividiert durch 1 mit 3 Nullen 1e-3 === 1 / 1000; // 0,001 // -6 dividiert durch 1 mit 6 Nullen 1,23e-6 === 1,23 / 1000000; // 0,00000123 // ein Beispiel mit einer größeren Zahl 1234e-2 === 1234 / 100; // 12,34, Dezimalpunkt bewegt sich 2 Mal
Hexadezimale Zahlen werden in JavaScript häufig zur Darstellung von Farben, zur Kodierung von Zeichen und für viele andere Zwecke verwendet. Daher gibt es natürlich auch eine kürzere Schreibweise: 0x
und dann die Zahl.
Zum Beispiel:
alarm( 0xff ); // 255 alarm( 0xFF ); // 255 (das Gleiche, Groß- und Kleinschreibung spielt keine Rolle)
Binäre und oktale Zahlensysteme werden selten verwendet, werden aber auch mit den Präfixen 0b
und 0o
unterstützt:
sei a = 0b11111111; // binäre Form von 255 sei b = 0o377; // Oktalform von 255 alarm( a == b ); // wahr, die gleiche Zahl 255 auf beiden Seiten
Es gibt nur 3 Zahlensysteme mit einer solchen Unterstützung. Für andere Zahlensysteme sollten wir die Funktion parseInt
verwenden (die wir später in diesem Kapitel sehen werden).
Die Methode num.toString(base)
gibt eine String-Darstellung von num
im Zahlensystem mit der angegebenen base
zurück.
Zum Beispiel:
sei num = 255; alarm( num.toString(16) ); // ff alarm( num.toString(2) ); // 11111111
Die base
kann zwischen 2
und 36
variieren. Standardmäßig ist es 10
.
Häufige Anwendungsfälle hierfür sind:
base=16 wird für Hex-Farben, Zeichenkodierungen usw. verwendet, Ziffern können 0..9
oder A..F
sein.
base=2 dient hauptsächlich dem Debuggen bitweiser Operationen, Ziffern können 0
oder 1
sein.
Basis=36 ist das Maximum, die Ziffern können 0..9
oder A..Z
sein. Zur Darstellung einer Zahl wird das gesamte lateinische Alphabet verwendet. Ein lustiger, aber nützlicher Fall für 36
ist, wenn wir einen langen numerischen Bezeichner in etwas Kürzeres umwandeln müssen, um beispielsweise eine kurze URL zu erstellen. Man kann es einfach im Zahlensystem mit der Basis 36
darstellen:
alarm( 123456..toString(36) ); // 2n9c
Zwei Punkte zum Aufrufen einer Methode
Bitte beachten Sie, dass zwei Punkte in 123456..toString(36)
kein Tippfehler sind. Wenn wir eine Methode direkt für eine Zahl aufrufen möchten, wie toString
im obigen Beispiel, müssen wir zwei Punkte dahinter platzieren ..
Wenn wir einen einzelnen Punkt platzieren würden: 123456.toString(36)
, würde ein Fehler auftreten, da die JavaScript-Syntax den Dezimalteil nach dem ersten Punkt impliziert. Und wenn wir noch einen Punkt setzen, dann weiß JavaScript, dass der Dezimalteil leer ist und führt nun die Methode aus.
Könnte auch schreiben (123456).toString(36)
.
Eine der am häufigsten verwendeten Operationen beim Arbeiten mit Zahlen ist das Runden.
Es gibt mehrere integrierte Funktionen zum Runden:
Math.floor
Abgerundet: 3.1
wird zu 3
und -1.1
wird zu -2
.
Math.ceil
Aufgerundet: 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
. In den mittleren Fällen wird bei 3.5
auf 4
aufgerundet und bei -3.5
auf -3
aufgerundet.
Math.trunc
(wird vom Internet Explorer nicht unterstützt)
Entfernt alles nach dem Komma ohne Rundung: 3.1
wird zu 3
, -1.1
wird zu -1
.
Hier ist die Tabelle, um die Unterschiede zwischen ihnen zusammenzufassen:
Math.floor | Math.ceil | Math.round | Math.trunc | |
---|---|---|---|---|
3.1 | 3 | 4 | 3 | 3 |
3.5 | 3 | 4 | 4 | 3 |
3.6 | 3 | 4 | 4 | 3 |
-1.1 | -2 | -1 | -1 | -1 |
-1.5 | -2 | -1 | -1 | -1 |
-1.6 | -2 | -1 | -2 | -1 |
Diese Funktionen decken alle Möglichkeiten ab, mit dem Dezimalteil einer Zahl umzugehen. Was aber, wenn wir die Zahl auf die n-th
Dezimalstelle runden möchten?
Zum Beispiel haben wir 1.2345
und möchten es auf zwei Ziffern runden, sodass wir nur 1.23
erhalten.
Dafür gibt es zwei Möglichkeiten:
Multiplizieren und dividieren.
Um beispielsweise die Zahl auf die 2. Nachkommastelle zu runden, können wir die Zahl mit 100
multiplizieren, die Rundungsfunktion aufrufen und sie dann zurückdividieren.
sei num = 1,23456; alarm( Math.round(num * 100) / 100 ); // 1,23456 -> 123,456 -> 123 -> 1,23
Die Methode toFixed(n) rundet die Zahl auf n
Stellen nach dem Komma und gibt eine String-Darstellung des Ergebnisses zurück.
sei num = 12,34; alarm( num.toFixed(1) ); // "12.3"
Dies rundet auf den nächsten Wert auf oder ab, ähnlich wie bei Math.round
:
sei num = 12,36; alarm( 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 am Ende Nullen angehängt:
sei num = 12,34; alarm( num.toFixed(5) ); // „12.34000“, Nullen hinzugefügt, um genau 5 Ziffern zu erhalten
Wir können es mit dem unären Plus oder einem Number()
-Aufruf in eine Zahl umwandeln, z. B. write +num.toFixed(5)
.
Intern wird eine Zahl im 64-Bit-Format IEEE-754 dargestellt, es gibt also genau 64 Bit zum Speichern einer Zahl: 52 davon werden zum Speichern der Ziffern verwendet, 11 davon speichern die Position des Dezimalpunkts und 1 Bit ist für das Zeichen.
Wenn eine Zahl wirklich groß ist, kann es sein, dass sie den 64-Bit-Speicher überläuft und zu einem speziellen numerischen Wert Infinity
wird:
Warnung( 1e500 ); // Unendlichkeit
Was vielleicht etwas weniger offensichtlich ist, aber recht häufig vorkommt, ist der Verlust an Präzision.
Betrachten Sie diesen (falschen!) Gleichheitstest:
alarm( 0,1 + 0,2 == 0,3 ); // FALSCH
Das ist richtig, wenn wir prüfen, ob die Summe von 0.1
und 0.2
0.3
ist, erhalten wir false
.
Seltsam! Was ist es dann, wenn nicht 0.3
?
Warnung( 0,1 + 0,2 ); // 0,30000000000000004
Autsch! Stellen Sie sich vor, Sie erstellen eine E-Shopping-Website und der Besucher legt Waren $0.10
und $0.20
in seinen Warenkorb. Der Gesamtbetrag der Bestellung beträgt $0.30000000000000004
. Das würde jeden überraschen.
Aber warum passiert das?
Eine Zahl wird in binärer Form im Speicher 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ärform tatsächlich unendliche Brüche.
alarm(0.1.toString(2)); // 0,0001100110011001100110011001100110011001100110011001101 alarm(0.2.toString(2)); // 0,001100110011001100110011001100110011001100110011001101 alarm((0.1 + 0.2).toString(2)); // 0,0100110011001100110011001100110011001100110011001101
Was ist 0.1
? Es ist eins geteilt durch zehn 1/10
, ein Zehntel. Im dezimalen Zahlensystem sind solche Zahlen leicht darstellbar. Vergleichen Sie es mit einem Drittel: 1/3
. Es wird ein endloser Bruch 0.33333(3)
.
Die Division durch 10
funktioniert im Dezimalsystem garantiert gut, die Division durch 3
jedoch nicht. Aus dem gleichen Grund funktioniert im binären Zahlensystem die Division durch 2
garantiert, aber 1/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 wenig wie es keine Möglichkeit gibt, ein Drittel als Dezimalbruch zu speichern.
Das Zahlenformat IEEE-754 löst dieses Problem durch Aufrunden auf die nächstmögliche Zahl. Diese Rundungsregeln erlauben es uns normalerweise nicht, diesen „winzigen Präzisionsverlust“ zu erkennen, aber er existiert.
Wir können dies in Aktion sehen:
alarm( 0.1.toFixed(20) ); // 0,10000000000000000555
Und wenn wir zwei Zahlen addieren, summieren sich ihre „Präzisionsverluste“.
Deshalb ist 0.1 + 0.2
nicht genau 0.3
.
Nicht nur JavaScript
Das gleiche Problem besteht in vielen anderen Programmiersprachen.
PHP, Java, C, Perl und Ruby liefern genau das gleiche Ergebnis, da sie auf demselben numerischen Format basieren.
Können wir das Problem umgehen? Sicherlich ist die zuverlässigste Methode, das Ergebnis mit Hilfe einer Methode toFixed(n) zu runden:
sei Summe = 0,1 + 0,2; alarm( sum.toFixed(2) ); // „0,30“
Bitte beachten Sie, dass toFixed
immer einen String zurückgibt. Es stellt sicher, dass es zwei Nachkommastellen hat. Das ist tatsächlich praktisch, wenn wir einen E-Shopping-Vorgang haben und $0.30
vorweisen müssen. In anderen Fällen können wir das unäre Plus verwenden, um es in eine Zahl umzuwandeln:
sei Summe = 0,1 + 0,2; alarm( +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 umzuwandeln, die Berechnungen durchführen und dann zurückdividieren. Wenn wir dann mit ganzen Zahlen rechnen, nimmt der Fehler etwas ab, aber wir erhalten ihn immer noch bei der Division:
alarm( (0,1 * 10 + 0,2 * 10) / 10 ); // 0,3 alarm( (0,28 * 100 + 0,14 * 100) / 100); // 0,4200000000000001
Der Multiplikations-/Divisionsansatz verringert also den Fehler, beseitigt ihn jedoch nicht vollständig.
Manchmal könnten wir versuchen, Brüche überhaupt zu umgehen. Wenn wir es beispielsweise mit einem Geschäft zu tun haben, können wir die Preise in Cent statt in Dollar speichern. Was aber, wenn wir einen Rabatt von 30 % gewähren? In der Praxis ist es selten möglich, Brüche völlig zu umgehen. Runden Sie sie einfach ab, um bei Bedarf „Schwänze“ abzuschneiden.
Das Lustige daran
Versuchen Sie Folgendes auszuführen:
// Hallo! Ich bin eine sich selbst steigernde Nummer! Warnung( 9999999999999999 ); // zeigt 10000000000000000
Dies hat das gleiche Problem: einen Verlust an Präzision. Es gibt 64 Bits für die Zahl, 52 davon können zum Speichern von Ziffern verwendet werden, aber das reicht nicht aus. Die niedrigstwertigen Ziffern verschwinden also.
JavaScript löst bei solchen Ereignissen keinen Fehler aus. Es gibt sein Bestes, die Zahl in das gewünschte Format zu bringen, aber leider ist dieses Format nicht groß genug.
Zwei Nullen
Eine weitere lustige Konsequenz der internen Darstellung von Zahlen ist die Existenz von zwei Nullen: 0
und -0
.
Das liegt daran, dass ein Vorzeichen durch ein einzelnes Bit dargestellt wird und daher für jede Zahl, einschließlich einer Null, gesetzt oder nicht gesetzt werden kann.
In den meisten Fällen ist der Unterschied nicht wahrnehmbar, da die Betreiber geeignet sind, sie als gleich zu behandeln.
Erinnern Sie sich an diese beiden besonderen numerischen Werte?
Infinity
(und -Infinity
) ist ein spezieller numerischer Wert, der größer (kleiner) als alles andere ist.
NaN
stellt einen Fehler dar.
Sie gehören zum Typ number
, sind aber keine „normalen“ Nummern, daher gibt es spezielle Funktionen, um sie zu überprüfen:
isNaN(value)
wandelt sein Argument in eine Zahl um und testet es dann auf NaN
:
alarm( isNaN(NaN) ); // WAHR alarm( isNaN("str") ); // WAHR
Aber brauchen wir diese Funktion? Können wir nicht einfach den Vergleich === NaN
verwenden? Leider nicht. Der Wert NaN
ist insofern einzigartig, als er nichts gleicht, auch nicht sich selbst:
alarm( NaN === NaN ); // FALSCH
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
:
alarm( isFinite("15") ); // WAHR alarm( isFinite("str") ); // false, weil ein besonderer Wert: NaN alarm( isFinite(Infinity) ); // false, weil ein besonderer Wert: Infinity
Manchmal wird isFinite
verwendet, um zu überprüfen, ob ein String-Wert eine reguläre Zahl ist:
let num = +prompt("Geben Sie eine Zahl ein", ''); // ist wahr, es sei denn, Sie geben Infinity, -Infinity oder keine Zahl ein alarm( isFinite(num) );
Bitte beachten Sie, dass eine leere oder nur aus Leerzeichen bestehende Zeichenfolge in allen numerischen Funktionen, einschließlich isFinite
als 0
behandelt wird.
Number.isNaN
und Number.isFinite
Die Methoden Number.isNaN und Number.isFinite sind die „strengeren“ Versionen der Funktionen isNaN
und isFinite
. Sie konvertieren ihr Argument nicht automatisch in eine Zahl, sondern prüfen stattdessen, ob es zum number
gehört.
Number.isNaN(value)
gibt true
zurück, wenn das Argument zum number
gehört und NaN
ist. In allen anderen Fällen wird false
zurückgegeben.
alarm( Number.isNaN(NaN) ); // WAHR alarm( Number.isNaN("str" / 2) ); // WAHR // Beachten Sie den Unterschied: alarm( Number.isNaN("str") ); // false, da „str“ zum String-Typ und nicht zum Zahlentyp gehört alarm( isNaN("str") ); // wahr, weil isNaN die Zeichenfolge „str“ in eine Zahl umwandelt und als Ergebnis dieser Konvertierung NaN erhält
Number.isFinite(value)
gibt true
zurück, wenn das Argument zum number
gehört und nicht NaN/Infinity/-Infinity
ist. In allen anderen Fällen wird false
zurückgegeben.
alarm( Number.isFinite(123) ); // WAHR alarm( Number.isFinite(Infinity) ); // FALSCH alarm( Number.isFinite(2 / 0) ); // FALSCH // Beachten Sie den Unterschied: alarm( Number.isFinite("123") ); // false, da „123“ zum String-Typ und nicht zum Zahlentyp gehört alarm( isFinite("123") ); // wahr, weil isFinite die Zeichenfolge „123“ in eine Zahl 123 umwandelt
In gewisser Weise sind Number.isNaN
und Number.isFinite
einfacher und unkomplizierter als die Funktionen isNaN
und isFinite
. In der Praxis werden jedoch meist isNaN
und isFinite
verwendet, da sie kürzer zu schreiben sind.
Vergleich mit Object.is
Es gibt eine spezielle integrierte 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 ist das richtig, weil die Zahl intern ein Vorzeichenbit hat, 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
.
Wir erwähnen Object.is
hier, da es häufig in JavaScript-Spezifikationen verwendet wird. Wenn ein interner Algorithmus zwei Werte auf exakte Gleichheit vergleichen muss, verwendet er Object.is
(intern SameValue genannt).
Die numerische Konvertierung mit einem Pluszeichen +
oder Number()
ist strikt. Wenn ein Wert nicht genau eine Zahl ist, schlägt er fehl:
alarm( +"100px" ); // NaN
Die einzige Ausnahme bilden Leerzeichen am Anfang oder am Ende der Zeichenfolge, da diese ignoriert werden.
Aber im wirklichen 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, wir haben also "19€"
und möchten daraus einen numerischen Wert extrahieren.
Dafür sind parseInt
und parseFloat
da.
Sie „lesen“ eine Zahl aus einer Zeichenfolge, bis sie es nicht mehr können. Im Fehlerfall wird die erfasste Nummer zurückgegeben. Die Funktion parseInt
gibt eine Ganzzahl zurück, während parseFloat
eine Gleitkommazahl zurückgibt:
alarm( parseInt('100px') ); // 100 alarm( parseFloat('12.5em') ); // 12.5 alarm( parseInt('12.3') ); // 12, nur der ganzzahlige Teil wird zurückgegeben alarm( parseFloat('12.3.4') ); // 12.3, der zweite Punkt stoppt das Lesen
Es gibt Situationen, in denen parseInt/parseFloat
NaN
zurückgibt. Es passiert, wenn keine Ziffern gelesen werden konnten:
alarm( parseInt('a123') ); // NaN, das erste Symbol stoppt den Prozess
Das zweite Argument von parseInt(str, radix)
Die Funktion parseInt()
verfügt über einen optionalen zweiten Parameter. Es gibt die Basis des Zahlensystems an, sodass parseInt
auch Zeichenfolgen mit Hexadezimalzahlen, Binärzahlen usw. analysieren kann:
alarm( parseInt('0xff', 16) ); // 255 alarm( parseInt('ff', 16) ); // 255, ohne 0x funktioniert auch alarm( parseInt('2n9c', 36) ); // 123456
JavaScript verfügt über ein integriertes Math-Objekt, das eine kleine Bibliothek mathematischer Funktionen und Konstanten enthält.
Ein paar Beispiele:
Math.random()
Gibt eine Zufallszahl von 0 bis 1 zurück (ohne 1).
alarm( Math.random() ); // 0,1234567894322 alarm( Math.random() ); // 0,5435252343232 alarm( Math.random() ); // ... (beliebige Zufallszahlen)
Math.max(a, b, c...)
und Math.min(a, b, c...)
Gibt das Größte und Kleinste aus der beliebigen Anzahl von Argumenten zurück.
alarm( Math.max(3, 5, -10, 0, 1) ); // 5 alarm( Math.min(1, 2) ); // 1
Math.pow(n, power)
Gibt n
in der angegebenen Potenz zurück.
alarm( Math.pow(2, 10) ); // 2 hoch 10 = 1024
Es gibt weitere Funktionen und Konstanten im Math
Objekt, einschließlich Trigonometrie, die Sie in den Dokumenten für das Math-Objekt finden.
So schreiben Sie Zahlen mit vielen Nullen:
Hängen Sie "e"
mit der Anzahl der Nullen an die Zahl an. Zum Beispiel: 123e6
ist dasselbe wie 123
mit 6 Nullen 123000000
.
Eine negative Zahl nach "e"
führt dazu, dass die Zahl bei gegebenen Nullen durch 1 geteilt wird. Beispielsweise bedeutet 123e-6
0.000123
( 123
Millionstel).
Für verschiedene Zahlensysteme:
Kann Zahlen direkt in Hex- ( 0x
), Oktal- ( 0o
) und Binärsystemen ( 0b
) schreiben.
parseInt(str, base)
analysiert die Zeichenfolge str
in eine Ganzzahl im Zahlensystem mit gegebener base
, 2 ≤ base ≤ 36
.
num.toString(base)
wandelt eine Zahl in einen String im Zahlensystem mit der angegebenen base
um.
Für regelmäßige Zahlentests:
isNaN(value)
wandelt sein Argument in eine Zahl um und testet es dann auf NaN
Number.isNaN(value)
prüft, ob sein Argument zum number
gehört, und wenn ja, testet es, ob es NaN
ist
isFinite(value)
wandelt sein Argument in eine Zahl um und prüft dann, ob es nicht NaN/Infinity/-Infinity
ist
Number.isFinite(value)
prüft, ob sein Argument zum number
gehört, und wenn ja, prüft es, ob es nicht NaN/Infinity/-Infinity
ist
Um Werte wie 12pt
und 100px
in eine Zahl umzuwandeln:
Verwenden Sie parseInt/parseFloat
für die „sanfte“ 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
oder num.toFixed(precision)
.
Denken Sie daran, dass es bei der Arbeit mit Brüchen zu Präzisionsverlusten kommt.
Weitere mathematische Funktionen:
Sehen Sie sich das Math-Objekt an, wenn Sie es brauchen. Die Bibliothek ist sehr klein, kann aber den Grundbedarf decken.
Wichtigkeit: 5
Erstellen Sie ein Skript, das den Besucher zur Eingabe zweier Zahlen auffordert und dann deren Summe anzeigt.
Führen Sie die Demo aus
PS Es gibt ein Problem mit Typen.
let a = +prompt("Die erste Zahl?", ""); let b = +prompt("Die zweite Zahl?", ""); alarm( a + b );
Beachten Sie das unäre Plus +
vor prompt
. Der Wert wird sofort in eine Zahl umgewandelt.
Andernfalls wären a
und b
Zeichenfolgen, deren Summe ihre Verkettung wäre, das heißt: "1" + "2" = "12"
.
Wichtigkeit: 4
Laut der Dokumentation runden Math.round
und toFixed
beide auf die nächste Zahl: 0..4
führt nach unten, während 5..9
nach oben führt.
Zum Beispiel:
alarm( 1.35.toFixed(1) ); // 1.4
Warum wird im ähnlichen Beispiel unten 6.35
auf 6.3
und nicht auf 6.4
gerundet?
alarm( 6.35.toFixed(1) ); // 6.3
Wie rundet man 6.35
richtig?
Intern ist der Dezimalbruch 6.35
ein endloser Binärbruch. Wie immer in solchen Fällen erfolgt die Speicherung mit Präzisionsverlust.
Mal sehen:
alarm( 6.35.toFixed(20) ); // 6.34999999999999964473
Der Präzisionsverlust kann sowohl zu einer Erhöhung als auch zu einer Verringerung einer Zahl führen. In diesem speziellen Fall wird die Zahl etwas kleiner, deshalb wurde abgerundet.
Und was kostet 1.35
?
alarm( 1.35.toFixed(20) ); // 1,35000000000000008882
Hier wurde die Zahl durch den Präzisionsverlust etwas größer, sodass aufgerundet wurde.
Wie können wir das Problem mit 6.35
beheben, wenn wir möchten, dass es richtig gerundet wird?
Wir sollten es vor dem Runden näher an eine ganze Zahl bringen:
alarm( (6.35 * 10).toFixed(20) ); // 63.50000000000000000000
Beachten Sie, dass 63.5
überhaupt keinen Präzisionsverlust aufweist. Das liegt daran, dass der Dezimalteil 0.5
tatsächlich 1/2
ist. Durch 2
dividierte Brüche werden im Binärsystem exakt dargestellt, jetzt können wir es runden:
alarm( Math.round(6.35 * 10) / 10 ); // 6,35 -> 63,5 -> 64 (gerundet) -> 6,4
Wichtigkeit: 5
Erstellen Sie eine Funktion readNumber
, die zur Eingabe einer Zahl auffordert, bis der Besucher einen gültigen numerischen Wert eingibt.
Der resultierende Wert muss als Zahl zurückgegeben werden.
Der Besucher kann den Vorgang auch abbrechen, indem er eine leere Zeile eingibt oder „ABBRECHEN“ drückt. In diesem Fall sollte die Funktion null
zurückgeben.
Führen Sie die Demo aus
Öffnen Sie eine Sandbox mit Tests.
Funktion readNumber() { lass num; Tun { num = prompt("Geben Sie bitte eine Zahl ein?", 0); } while ( !isFinite(num) ); if (num === null || num === '') return null; return +num; } alarm(`Lesen: ${readNumber()}`);
Die Lösung ist etwas komplizierter als sie sein könnte, da wir mit null
/Leerzeilen umgehen müssen.
Wir akzeptieren also die Eingabe tatsächlich, bis es eine „reguläre Zahl“ ist. Sowohl null
(Abbrechen) als auch leere Zeile erfüllen diese Bedingung ebenfalls, da sie in numerischer Form 0
sind.
Nachdem wir aufgehört haben, müssen wir null
und leere Zeilen speziell behandeln ( null
zurückgeben), da die Konvertierung in eine Zahl 0
zurückgeben würde.
Öffnen Sie die Lösung mit Tests in einer Sandbox.
Wichtigkeit: 4
Diese Schleife ist unendlich. Es endet nie. Warum?
sei i = 0; while (i != 10) { ich += 0,2; }
Das liegt daran, dass i
niemals 10
erreichen würde.
Führen Sie es aus, um die tatsächlichen Werte von i
anzuzeigen:
sei i = 0; while (i < 11) { ich += 0,2; if (i > 9.8 && i < 10.2) alarm( i ); }
Keiner von ihnen ist genau 10
.
Solche Dinge passieren aufgrund der Genauigkeitsverluste beim Addieren von Brüchen wie 0.2
.
Fazit: Vermeiden Sie Gleichheitsprüfungen, wenn Sie mit Dezimalbrüchen arbeiten.
Wichtigkeit: 2
Die integrierte Funktion Math.random()
erstellt einen Zufallswert von 0
bis 1
(ohne 1
).
Schreiben Sie die Funktion random(min, max)
um eine zufällige Gleitkommazahl von min
bis max
zu generieren (ohne max
).
Beispiele seiner Arbeit:
alarm( random(1, 5) ); // 1.2345623452 alarm( random(1, 5) ); // 3.7894332423 alarm( random(1, 5) ); // 4.3435234525
Wir müssen alle Werte aus dem Intervall 0…1 in Werte von min
bis max
„abbilden“.
Dies kann in zwei Schritten erfolgen:
Wenn wir eine Zufallszahl von 0…1 mit max-min
multiplizieren, dann erhöht sich das Intervall der möglichen Werte von 0..1
auf 0..max-min
.
Wenn wir nun min
hinzufügen, wird das mögliche Intervall von min
bis max
.
Die Funktion:
Funktion random(min, max) { return min + Math.random() * (max - min); } alarm( random(1, 5) ); alarm( random(1, 5) ); alarm( random(1, 5) );
Wichtigkeit: 2
Erstellen Sie eine Funktion randomInteger(min, max)
die eine zufällige Ganzzahl von min
bis max
generiert, die sowohl min
als auch max
als mögliche Werte enthält.
Jede Zahl aus dem Intervall min..max
muss mit der gleichen Wahrscheinlichkeit auftreten.
Beispiele seiner Arbeit:
alarm( randomInteger(1, 5) ); // 1 alarm( randomInteger(1, 5) ); // 3 alarm( randomInteger(1, 5) ); // 5
Als Grundlage können Sie die Lösung der vorherigen Aufgabe verwenden.
Die einfachste, aber falsche Lösung wäre, einen Wert von min
nach max
zu generieren und ihn zu runden:
Funktion randomInteger(min, max) { let rand = min + Math.random() * (max - min); return Math.round(rand); } alarm( randomInteger(1, 3) );
Die Funktion funktioniert, ist aber fehlerhaft. Die Wahrscheinlichkeit, die Kantenwerte min
und max
zu erhalten, ist doppelt so gering wie bei allen anderen.
Wenn Sie das obige Beispiel mehrmals ausführen, werden Sie leicht erkennen, dass 2
am häufigsten vorkommt.
Das passiert, weil Math.round()
Zufallszahlen aus dem Intervall 1..3
erhält und diese wie folgt rundet:
Werte von 1 ... bis 1,4999999999 werden zu 1 Werte von 1,5 ... bis 2,4999999999 werden zu 2 Werte von 2,5 ... bis 2,9999999999 werden zu 3
Jetzt können wir deutlich erkennen, dass 1
doppelt so viele Werte erhält wie 2
. Und das Gleiche gilt auch für 3
.
Es gibt viele richtige Lösungen für die Aufgabe. Eine davon besteht darin, Intervallgrenzen anzupassen. Um gleiche Intervalle zu gewährleisten, können wir Werte von 0.5 to 3.5
generieren und so den Kanten die erforderlichen Wahrscheinlichkeiten hinzufügen:
Funktion randomInteger(min, max) { // jetzt ist Rand von (min-0,5) bis (max+0,5) sei rand = min - 0,5 + Math.random() * (max - min + 1); return Math.round(rand); } alarm( randomInteger(1, 3) );
Eine alternative Möglichkeit könnte darin bestehen, Math.floor
für eine Zufallszahl von min
bis max+1
zu verwenden:
Funktion randomInteger(min, max) { // hier ist Rand von min bis (max+1) let rand = min + Math.random() * (max + 1 - min); return Math.floor(rand); } alarm( randomInteger(1, 3) );
Jetzt werden alle Intervalle folgendermaßen abgebildet:
Werte von 1 ... bis 1,9999999999 werden zu 1 Werte von 2 ... bis 2,9999999999 werden zu 2 Werte von 3 ... bis 3,9999999999 werden zu 3
Alle Intervalle haben die gleiche Länge, sodass die endgültige Verteilung gleichmäßig ist.