5. Neue Zahlen- und Math-Funktionen
Inhaltsverzeichnis
Bitte unterstützen Sie dieses Buch: kaufen Sie es (PDF, EPUB, MOBI) oder spenden Sie
(Werbung, bitte nicht blockieren.)

5. Neue Zahlen- und Math-Funktionen



5.1 Überblick

5.1.1 Neue Ganzzahl-Literale

Sie können jetzt Ganzzahlen in binärer und oktaler Notation angeben

> 0xFF // ES5: hexadecimal
255
> 0b11 // ES6: binary
3
> 0o10 // ES6: octal
8

5.1.2 Neue Number-Eigenschaften

Das globale Objekt Number hat einige neue Eigenschaften erhalten

5.1.3 Neue Math-Methoden

Das globale Objekt Math hat neue Methoden für numerische, trigonometrische und bitweise Operationen. Sehen wir uns vier Beispiele an.

Math.sign() gibt das Vorzeichen einer Zahl zurück

> Math.sign(-8)
-1
> Math.sign(0)
0
> Math.sign(3)
1

Math.trunc() entfernt den Dezimalbruch einer Zahl

> Math.trunc(3.1)
3
> Math.trunc(3.9)
3
> Math.trunc(-3.1)
-3
> Math.trunc(-3.9)
-3

Math.log10() berechnet den Logarithmus zur Basis 10

> Math.log10(100)
2

Math.hypot() berechnet die Quadratwurzel der Summe der Quadrate seiner Argumente (Satz des Pythagoras)

> Math.hypot(3, 4)
5    

5.2 Neue Ganzzahl-Literale

ECMAScript 5 hat bereits Literale für hexadezimale Ganzzahlen

> 0x9
9
> 0xA
10
> 0x10
16
> 0xFF
255

ECMAScript 6 bringt zwei neue Arten von Ganzzahl-Literalen

Denken Sie daran, dass die Methode Number.toString(radix) verwendet werden kann, um Zahlen in einer anderen Basis als 10 anzuzeigen

> 255..toString(16)
'ff'
> 4..toString(2)
'100'
> 8..toString(8)
'10'

(Die doppelten Punkte sind notwendig, damit der Punkt für den Eigenschaftszugriff nicht mit einem Dezimalpunkt verwechselt wird.)

5.2.1 Anwendungsfall für Oktal-Literale: Unix-Dateiberechtigungen

Im Node.js Dateisystemmodul haben mehrere Funktionen den Parameter mode. Sein Wert wird verwendet, um Dateiberechtigungen anzugeben, über eine Kodierung, die ein Überbleibsel von Unix ist.

Das bedeutet, dass Berechtigungen durch 9 Bits dargestellt werden können (3 Kategorien mit je 3 Berechtigungen)

  Benutzer Gruppe Alle
Berechtigungen r, w, x r, w, x r, w, x
Bit 8, 7, 6 5, 4, 3 2, 1, 0

Die Berechtigungen einer einzelnen Benutzerkategorie werden in 3 Bits gespeichert

Bits Berechtigungen Oktale Ziffer
000 ––– 0
001 ––x 1
010 –w– 2
011 –wx 3
100 r–– 4
101 r–x 5
110 rw– 6
111 rwx 7

Das bedeutet, dass Oktalzahlen eine kompakte Darstellung aller Berechtigungen sind, Sie benötigen nur 3 Ziffern, eine Ziffer pro Benutzerkategorie. Zwei Beispiele

5.2.2 Number.parseInt() und die neuen Ganzzahl-Literale

Number.parseInt() (das dasselbe tut wie die globale Funktion parseInt()) hat die folgende Signatur

Number.parseInt(string, radix?)
5.2.2.1 Number.parseInt(): Hexadezimale Zahlen-Literale

Number.parseInt() bietet spezielle Unterstützung für die hexadezimale Literalnotation – das Präfix 0x (oder 0X) von string wird entfernt, wenn

Zum Beispiel

> Number.parseInt('0xFF')
255
> Number.parseInt('0xFF', 0)
255
> Number.parseInt('0xFF', 16)
255

In allen anderen Fällen werden Ziffern nur bis zur ersten Nicht-Ziffer geparst

> Number.parseInt('0xFF', 10)
0
> Number.parseInt('0xFF', 17)
0
5.2.2.2 Number.parseInt(): Binäre und Oktale Zahlen-Literale

Allerdings hat Number.parseInt() keine spezielle Unterstützung für binäre oder oktale Literale!

> Number.parseInt('0b111')
0
> Number.parseInt('0b111', 2)
0
> Number.parseInt('111', 2)
7

> Number.parseInt('0o10')
0
> Number.parseInt('0o10', 8)
0
> Number.parseInt('10', 8)
8

Wenn Sie diese Arten von Literalen parsen möchten, müssen Sie Number() verwenden.

> Number('0b111')
7
> Number('0o10')
8

Number.parseInt() funktioniert gut mit Zahlen, die eine andere Basis haben, solange kein spezielles Präfix vorhanden ist und der Parameter radix angegeben ist.

> Number.parseInt('111', 2)
7
> Number.parseInt('10', 8)
8

5.3 Neue statische Number-Eigenschaften

Dieser Abschnitt beschreibt neue Eigenschaften, die der Konstruktor Number in ECMAScript 6 erhalten hat.

5.3.1 Früher globale Funktionen

Vier zahlenbezogene Funktionen sind bereits als globale Funktionen verfügbar und wurden Number als Methoden hinzugefügt: isFinite und isNaN, parseFloat und parseInt. Alle funktionieren fast genauso wie ihre globalen Gegenstücke, aber isFinite und isNaN erzwingen keine Typumwandlung ihrer Argumente mehr, was besonders wichtig für isNaN ist. Die folgenden Unterabschnitte erklären alle Details.

5.3.1.1 Number.isFinite(number)

Number.isFinite(number) bestimmt, ob number eine tatsächliche Zahl ist (weder Infinity noch -Infinity noch NaN).

> Number.isFinite(Infinity)
false
> Number.isFinite(-Infinity)
false
> Number.isFinite(NaN)
false
> Number.isFinite(123)
true

Der Vorteil dieser Methode ist, dass sie ihren Parameter nicht in eine Zahl umwandelt (während die globale Funktion dies tut).

> Number.isFinite('123')
false
> isFinite('123')
true
5.3.1.2 Number.isNaN(number)

Number.isNaN(number) prüft, ob number der Wert NaN ist.

Eine ES5-Möglichkeit, diese Prüfung durchzuführen, ist über !==.

> const x = NaN;
> x !== x
true

Eine aussagekräftigere Methode ist die globale Funktion isNaN().

> const x = NaN;
> isNaN(x)
true

Diese Funktion wandelt jedoch Nicht-Zahlen in Zahlen um und gibt true zurück, wenn das Ergebnis NaN ist (was normalerweise nicht erwünscht ist).

> isNaN('???')
true

Die neue Methode Number.isNaN() weist dieses Problem nicht auf, da sie ihre Argumente nicht in Zahlen umwandelt.

> Number.isNaN('???')
false
5.3.1.3 Number.parseFloat und Number.parseInt

Die folgenden beiden Methoden funktionieren exakt wie die globalen Funktionen mit denselben Namen. Sie wurden aus Vollständigkeitsgründen zu Number hinzugefügt; jetzt sind alle zahlenbezogenen Funktionen dort verfügbar.

5.3.2 Number.EPSILON

Besonders bei Dezimalbrüchen können Rundungsfehler in JavaScript zu einem Problem werden3. Zum Beispiel können 0.1 und 0.2 nicht präzise dargestellt werden, was Sie bemerken, wenn Sie sie addieren und mit 0.3 vergleichen (das ebenfalls nicht präzise dargestellt werden kann).

> 0.1 + 0.2 === 0.3
false

Number.EPSILON gibt eine angemessene Fehlertoleranz beim Vergleichen von Fließkommazahlen an. Es bietet eine bessere Möglichkeit, Fließkommawerte zu vergleichen, wie die folgende Funktion zeigt.

function epsEqu(x, y) {
    return Math.abs(x - y) < Number.EPSILON;
}
console.log(epsEqu(0.1+0.2, 0.3)); // true

5.3.3 Number.isInteger(number)

JavaScript hat nur Fließkommazahlen (Doubles). Dementsprechend sind Ganzzahlen einfach Fließkommazahlen ohne Dezimalbruch.

Number.isInteger(number) gibt true zurück, wenn number eine Zahl ist und keinen Dezimalbruch hat.

> Number.isInteger(-17)
true
> Number.isInteger(33)
true
> Number.isInteger(33.1)
false
> Number.isInteger('33')
false
> Number.isInteger(NaN)
false
> Number.isInteger(Infinity)
false

5.3.4 Sichere Ganzzahlen

JavaScript-Zahlen haben nur genügend Speicherplatz, um 53-Bit-Ganzzahlen mit Vorzeichen darzustellen. Das heißt, Ganzzahlen i im Bereich −253 < i < 253 sind sicher. Was genau das bedeutet, wird gleich erklärt. Die folgenden Eigenschaften helfen dabei, festzustellen, ob eine JavaScript-Ganzzahl sicher ist.

Die Vorstellung von sicheren Ganzzahlen konzentriert sich darauf, wie mathematische Ganzzahlen in JavaScript dargestellt werden. Im Bereich (−253, 253) (ohne die untere und obere Grenze) sind JavaScript-Ganzzahlen sicher: Es gibt eine Eins-zu-eins-Zuordnung zwischen ihnen und den mathematischen Ganzzahlen, die sie darstellen.

Außerhalb dieses Bereichs sind JavaScript-Ganzzahlen unsicher: Zwei oder mehr mathematische Ganzzahlen werden als dieselbe JavaScript-Ganzzahl dargestellt. Beispielsweise kann JavaScript ab 253 nur jede zweite mathematische Ganzzahl darstellen.

> Math.pow(2, 53)
9007199254740992

> 9007199254740992
9007199254740992
> 9007199254740993
9007199254740992
> 9007199254740994
9007199254740994
> 9007199254740995
9007199254740996
> 9007199254740996
9007199254740996
> 9007199254740997
9007199254740996

Daher ist eine sichere JavaScript-Ganzzahl eine, die eindeutig eine einzelne mathematische Ganzzahl darstellt.

Die beiden statischen Number-Eigenschaften, die die untere und obere Grenze sicherer Ganzzahlen angeben, könnten wie folgt definiert werden.

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

Number.isSafeInteger() bestimmt, ob eine JavaScript-Zahl eine sichere Ganzzahl ist, und könnte wie folgt definiert werden.

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

5.3.4.2 Wann sind Berechnungen mit Ganzzahlen korrekt?

Wie können wir sicherstellen, dass die Ergebnisse von Berechnungen mit Ganzzahlen korrekt sind? Beispielsweise 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

Dieses Mal 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. Formeller ausgedrückt:

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

impliziert, dass a op b ein korrektes Ergebnis ist.

5.4 Neue Math-Funktionalität

Das globale Objekt Math hat in ECMAScript 6 mehrere neue Methoden.

5.4.1 Verschiedene numerische Funktionalitäten

5.4.1.1 Math.sign(x)

Math.sign(x) gibt zurück

Beispiele

> Math.sign(-8)
-1
> Math.sign(3)
1

> Math.sign(0)
0
> Math.sign(NaN)
NaN

> Math.sign(-Infinity)
-1
> Math.sign(Infinity)
1
5.4.1.2 Math.trunc(x)

Math.trunc(x) entfernt den Dezimalbruch von x. Ergänzt die anderen Rundungsmethoden Math.floor(), Math.ceil() und Math.round().

> Math.trunc(3.1)
3
> Math.trunc(3.9)
3
> Math.trunc(-3.1)
-3
> Math.trunc(-3.9)
-3

Sie könnten Math.trunc() wie folgt implementieren.

function trunc(x) {
    return Math.sign(x) * Math.floor(Math.abs(x));
}
5.4.1.3 Math.cbrt(x)

Math.cbrt(x) gibt die Kubikwurzel von x (∛x) zurück.

> Math.cbrt(8)
2

5.4.2 Verwendung von 0 statt 1 bei Potenzierung und Logarithmus

Ein kleiner Bruch kann genauer dargestellt werden, wenn er nach der Null kommt. Ich werde dies mit Dezimalbrüchen demonstrieren (JavaScript-Zahlen werden intern mit Basis 2 gespeichert, aber die gleiche Logik gilt auch dort).

Fließkommazahlen mit Basis 10 werden intern als Mantisse × 10Exponent dargestellt. Die Mantisse hat eine einzelne Ziffer vor dem Dezimalpunkt und der Exponent "verschiebt" den Punkt bei Bedarf. Das bedeutet, wenn Sie einen kleinen Bruch in die interne Darstellung umwandeln, führt eine Null vor dem Punkt zu einer kleineren Mantisse als eine Eins vor dem Punkt. Zum Beispiel:

Was die Genauigkeit betrifft, so ist die hier wichtige Größe die Kapazität der Mantisse, gemessen in signifikanten Ziffern. Deshalb bietet (A) eine höhere Genauigkeit als (B).

Darüber hinaus stellt JavaScript Zahlen nahe Null (z. B. kleine Brüche) mit höherer Genauigkeit dar.

5.4.2.1 Math.expm1(x)

Math.expm1(x) gibt Math.exp(x)-1 zurück. Die Umkehrfunktion von Math.log1p().

Daher bietet diese Methode eine höhere Genauigkeit, wenn Math.exp() Ergebnisse nahe 1 hat. Den Unterschied zwischen beiden können Sie in der folgenden Interaktion sehen.

> Math.expm1(1e-10)
1.00000000005e-10
> Math.exp(1e-10)-1
1.000000082740371e-10

Das erstere ist das bessere Ergebnis, was Sie durch die Verwendung einer Bibliothek (wie z. B. decimal.js) für Fließkommazahlen mit beliebiger Genauigkeit ("Bigfloats") verifizieren können.

> var Decimal = require('decimal.js').config({precision:50});
> new Decimal(1e-10).exp().minus(1).toString()
'1.000000000050000000001666666666708333333e-10'
5.4.2.2 Math.log1p(x)

Math.log1p(x) gibt Math.log(1 + x) zurück. Die Umkehrfunktion von Math.expm1().

Daher können Sie mit dieser Methode Parameter angeben, die nahe 1 liegen, mit höherer Genauigkeit. Die folgenden Beispiele zeigen, warum das so ist.

Die folgenden beiden Aufrufe von log() ergeben dasselbe Ergebnis.

> Math.log(1 + 1e-16)
0
> Math.log(1 + 0)
0

Im Gegensatz dazu liefert log1p() unterschiedliche Ergebnisse.

> Math.log1p(1e-16)
1e-16
> Math.log1p(0)
0

Der Grund für die höhere Genauigkeit von Math.log1p() ist, dass das korrekte Ergebnis für 1 + 1e-16 mehr signifikante Ziffern hat als 1e-16.

> 1 + 1e-16 === 1
true
> 1e-16 === 0
false

5.4.3 Logarithmen zur Basis 2 und 10

5.4.3.1 Math.log2(x)

Math.log2(x) berechnet den Logarithmus zur Basis 2.

> Math.log2(8)
3
5.4.3.2 Math.log10(x)

Math.log10(x) berechnet den Logarithmus zur Basis 10.

> Math.log10(100)
2

5.4.4 Unterstützung für die Kompilierung nach JavaScript

Emscripten war Vorreiter bei einem Programmierstil, der später von asm.js übernommen wurde: Die Operationen einer virtuellen Maschine (denken Sie an Bytecode) werden in einer statischen Teilmenge von JavaScript ausgedrückt. Diese Teilmenge kann von JavaScript-Engines effizient ausgeführt werden: Wenn sie das Ergebnis einer Kompilierung aus C++ ist, läuft sie mit etwa 70 % der nativen Geschwindigkeit.

Die folgenden Math-Methoden wurden hauptsächlich zur Unterstützung von asm.js und ähnlichen Kompilierungsstrategien hinzugefügt, sie sind für andere Anwendungen weniger nützlich.

5.4.4.1 Math.fround(x)

Math.fround(x) rundet x auf einen 32-Bit-Fließkommawert (float). Wird von asm.js verwendet, um eine Engine anzuweisen, intern einen float-Wert zu verwenden.

5.4.4.2 Math.imul(x, y)

Math.imul(x, y) multipliziert die beiden 32-Bit-Ganzzahlen x und y und gibt die unteren 32 Bits des Ergebnisses zurück. Dies ist die einzige 32-Bit-Grundrechenart, die nicht durch die Verwendung eines JavaScript-Operators und die Umwandlung des Ergebnisses zurück in 32 Bits simuliert werden kann. Beispielsweise könnte idiv wie folgt implementiert werden.

function idiv(x, y) {
    return (x / y) | 0;
}

Im Gegensatz dazu kann die Multiplikation zweier großer 32-Bit-Ganzzahlen eine Zahl ergeben, die so groß ist, dass untere Bits verloren gehen.

5.4.5 Bitweise Operationen

Warum ist das interessant? Zitat aus „Fast, Deterministic, and Portable Counting Leading Zeros“ von Miro Samek.

Das Zählen führender Nullen in einer Ganzzahl ist eine kritische Operation in vielen DSP-Algorithmen, wie z. B. der Normalisierung von Samples in der Audio- oder Videoverarbeitung, sowie in Echtzeitplanern, um schnell die Task mit der höchsten Priorität zu finden, die bereit zum Ausführen ist.

5.4.6 Trigonometrische Methoden

5.5 FAQ: Zahlen

5.5.1 Wie kann ich Ganzzahlen außerhalb des 53-Bit-Bereichs von JavaScript verwenden?

JavaScript-Ganzzahlen haben einen Bereich von 53 Bits. Das ist ein Problem, wenn 64-Bit-Ganzzahlen benötigt werden. Zum Beispiel: In seiner JSON-API musste Twitter von Ganzzahlen zu Strings wechseln, als Tweet-IDs zu groß wurden.

Derzeit gibt es keine Möglichkeit, diese Einschränkung zu umgehen, außer eine Bibliothek für höhere Genauigkeitszahlen (BigInts oder BigFloats) zu verwenden. Eine solche Bibliothek ist decimal.js.

Es gibt Pläne, größere Ganzzahlen in JavaScript zu unterstützen, aber die Umsetzung könnte noch einige Zeit dauern.

Weiter: 6. Neue String-Funktionen