JavaScript für ungeduldige Programmierer (ES2022-Ausgabe)
Bitte unterstützen Sie dieses Buch: kaufen Sie es oder spenden Sie
(Werbung, bitte nicht blockieren.)

16 Zahlen



JavaScript hat zwei Arten von numerischen Werten

Dieses Kapitel behandelt Zahlen. Bigints werden später in diesem Buch behandelt.

16.1 Zahlen werden sowohl für Gleitkommazahlen als auch für Ganzzahlen verwendet

Der Datentyp number wird in JavaScript sowohl für Ganzzahlen als auch für Gleitkommazahlen verwendet

98
123.45

Allerdings sind alle Zahlen Doubles, also 64-Bit-Gleitkommazahlen, die gemäß dem IEEE Standard for Floating-Point Arithmetic (IEEE 754) implementiert sind.

Ganzzahlen sind einfach Gleitkommazahlen ohne Dezimalbruch

> 98 === 98.0
true

Beachten Sie, dass die meisten JavaScript-Engines unter der Haube oft echte Ganzzahlen verwenden können, mit allen damit verbundenen Vorteilen in Bezug auf Leistung und Speicherplatz.

16.2 Zahlenliterale

Betrachten wir die Literale für Zahlen.

16.2.1 Ganzzahl-Literale

Mehrere Ganzzahl-Literale ermöglichen es uns, Ganzzahlen mit verschiedenen Basen auszudrücken

// Binary (base 2)
assert.equal(0b11, 3); // ES6

// Octal (base 8)
assert.equal(0o10, 8); // ES6

// Decimal (base 10)
assert.equal(35, 35);

// Hexadecimal (base 16)
assert.equal(0xE7, 231);

16.2.2 Gleitkomma-Literale

Gleitkommazahlen können nur in Basis 10 ausgedrückt werden.

Brüche

> 35.0
35

Exponent: eN bedeutet ×10N

> 3e2
300
> 3e-2
0.03
> 0.3e2
30

16.2.3 Syntax-Fallstrick: Eigenschaften von Ganzzahl-Literalen

Der Zugriff auf eine Eigenschaft eines Ganzzahl-Literals birgt einen Fallstrick: Wenn das Ganzzahl-Literal unmittelbar von einem Punkt gefolgt wird, wird dieser Punkt als Dezimalpunkt interpretiert.

7.toString(); // syntax error

Es gibt vier Möglichkeiten, diesen Fallstrick zu umgehen

7.0.toString()
(7).toString()
7..toString()
7 .toString()  // space before dot

16.2.4 Unterstriche (_) als Trennzeichen in Zahlenliteralen [ES2021]

Die Gruppierung von Ziffern zur besseren Lesbarkeit langer Zahlen hat eine lange Tradition. Zum Beispiel

Seit ES2021 können wir Unterstriche als Trennzeichen in Zahlenliteralen verwenden

const inhabitantsOfLondon = 1_335_000;
const distanceEarthSunInKm = 149_600_000;

Bei anderen Basen ist die Gruppierung ebenfalls wichtig

const fileSystemPermission = 0b111_111_000;
const bytes = 0b1111_10101011_11110000_00001101;
const words = 0xFAB_F00D;

Wir können das Trennzeichen auch in Brüchen und Exponenten verwenden

const massOfElectronInKg = 9.109_383_56e-31;
const trillionInShortScale = 1e1_2;
16.2.4.1 Wo können wir Trennzeichen setzen?

Die Positionen von Trennzeichen sind auf zwei Arten eingeschränkt

Die Motivation hinter diesen Einschränkungen ist, die Analyse einfach zu halten und seltsame Grenzfälle zu vermeiden.

16.2.4.2 Parsen von Zahlen mit Trennzeichen

Die folgenden Funktionen zum Parsen von Zahlen unterstützen keine Trennzeichen

Zum Beispiel

> Number('123_456')
NaN
> Number.parseInt('123_456')
123

Die Begründung ist, dass numerische Trennzeichen für Code gedacht sind. Andere Arten von Eingaben sollten anders verarbeitet werden.

16.3 Arithmetische Operatoren

16.3.1 Binäre arithmetische Operatoren

Tabelle 5 listet die binären arithmetischen Operatoren von JavaScript auf.

Tabelle 5: Binäre arithmetische Operatoren.
Operator Name Beispiel
n + m Addition ES1 3 + 4 7
n - m Subtraktion ES1 9 - 1 8
n * m Multiplikation ES1 3 * 2.25 6.75
n / m Division ES1 5.625 / 5 1.125
n % m Rest ES1 8 % 5 3
-8 % 5 -3
n ** m Potenzierung ES2016 4 ** 2 16
16.3.1.1 % ist ein Restoperator

% ist ein Restoperator, kein Modulo-Operator. Sein Ergebnis hat das Vorzeichen des ersten Operanden.

> 5 % 3
2
> -5 % 3
-2

Weitere Informationen über den Unterschied zwischen Rest und Modulo finden Sie im Blogbeitrag „Remainder operator vs. modulo operator (with JavaScript code)“ auf 2ality.

16.3.2 Unärer Plus (+) und Negation (-)

Tabelle 6 fasst die beiden Operatoren unärer Plus (+) und Negation (-) zusammen.

Tabelle 6: Die Operatoren unärer Plus (+) und Negation (-).
Operator Name Beispiel
+n Unärer Plus ES1 +(-7) -7
-n Unäre Negation ES1 -(-7) 7

Beide Operatoren konvertieren ihre Operanden zu Zahlen

> +'5'
5
> +'-12'
-12
> -'9'
-9

Somit ermöglicht uns der unäre Plus die Konvertierung beliebiger Werte in Zahlen.

16.3.3 Inkrementieren (++) und Dekrementieren (--)

Der Inkrementierungsoperator ++ existiert in einer Präfix- und einer Suffix-Version. In beiden Versionen addiert er zerstörend eins zu seinem Operanden. Daher muss sein Operand ein Speicherort sein, der verändert werden kann.

Der Dekrementierungsoperator -- funktioniert genauso, subtrahiert aber eins von seinem Operanden. Die nächsten beiden Beispiele erklären den Unterschied zwischen der Präfix- und der Suffix-Version.

Tabelle 7 fasst die Inkrementierungs- und Dekrementierungsoperatoren zusammen.

Tabelle 7: Inkrementierungsoperatoren und Dekrementierungsoperatoren.
Operator Name Beispiel
v++ Inkrementieren ES1 let v=0; [v++, v] [0, 1]
++v Inkrementieren ES1 let v=0; [++v, v] [1, 1]
v-- Dekrementieren ES1 let v=1; [v--, v] [1, 0]
--v Dekrementieren ES1 let v=1; [--v, v] [0, 0]

Als Nächstes betrachten wir Beispiele für die Verwendung dieser Operatoren.

Präfix-++ und Präfix--- ändern ihre Operanden und geben sie dann zurück.

let foo = 3;
assert.equal(++foo, 4);
assert.equal(foo, 4);

let bar = 3;
assert.equal(--bar, 2);
assert.equal(bar, 2);

Suffix-++ und Suffix--- geben ihre Operanden zurück und ändern sie dann.

let foo = 3;
assert.equal(foo++, 3);
assert.equal(foo, 4);

let bar = 3;
assert.equal(bar--, 3);
assert.equal(bar, 2);
16.3.3.1 Operanden: nicht nur Variablen

Wir können diese Operatoren auch auf Eigenschaftswerte anwenden

const obj = { a: 1 };
++obj.a;
assert.equal(obj.a, 2);

Und auf Array-Elemente

const arr = [ 4 ];
arr[0]++;
assert.deepEqual(arr, [5]);

  Übung: Zahlenoperatoren

exercises/numbers-math/is_odd_test.mjs

16.4 Umwandlung in Zahlen

Hier sind drei Möglichkeiten, Werte in Zahlen umzuwandeln

Empfehlung: Verwenden Sie die beschreibende Number(). Tabelle 8 fasst zusammen, wie sie funktioniert.

Tabelle 8: Umwandlung von Werten in Zahlen.
x Number(x)
undefined NaN
null 0
boolean false 0, true 1
number x (keine Änderung)
bigint -1n -1, 1n 1 usw.
string '' 0
Andere geparste Zahl, wobei führende/nachfolgende Leerzeichen ignoriert werden
symbol Löst TypeError aus
object Konfigurierbar (z. B. über .valueOf())

Beispiele

assert.equal(Number(123.45), 123.45);

assert.equal(Number(''), 0);
assert.equal(Number('\n 123.45 \t'), 123.45);
assert.equal(Number('xyz'), NaN);

assert.equal(Number(-123n), -123);

Wie Objekte in Zahlen umgewandelt werden, kann konfiguriert werden – zum Beispiel durch Überschreiben von .valueOf().

> Number({ valueOf() { return 123 } })
123

  Übung: Umwandlung in Zahlen

exercises/numbers-math/parse_number_test.mjs

16.5 Fehlerwerte

Zwei Zahlenwerte werden zurückgegeben, wenn Fehler auftreten

16.5.1 Fehlerwert: NaN

NaN ist eine Abkürzung für „Not a Number“. Ironischerweise betrachtet JavaScript es als eine Zahl.

> typeof NaN
'number'

Wann wird NaN zurückgegeben?

NaN wird zurückgegeben, wenn eine Zahl nicht geparst werden kann.

> Number('$$$')
NaN
> Number(undefined)
NaN

NaN wird zurückgegeben, wenn eine Operation nicht ausgeführt werden kann.

> Math.log(-1)
NaN
> Math.sqrt(-1)
NaN

NaN wird zurückgegeben, wenn ein Operand oder Argument NaN ist (um Fehler zu propagieren).

> NaN - 3
NaN
> 7 ** NaN
NaN
16.5.1.1 Prüfen auf NaN

NaN ist der einzige JavaScript-Wert, der nicht strikt gleich sich selbst ist.

const n = NaN;
assert.equal(n === n, false);

Hier sind mehrere Möglichkeiten zu prüfen, ob ein Wert x NaN ist

const x = NaN;

assert.equal(Number.isNaN(x), true); // preferred
assert.equal(Object.is(x, NaN), true);
assert.equal(x !== x, true);

In der letzten Zeile verwenden wir die Eigenart der Gleichheit, um NaN zu erkennen.

16.5.1.2 NaN in Arrays finden

Einige Array-Methoden können NaN nicht finden

> [NaN].indexOf(NaN)
-1

Andere können das.

> [NaN].includes(NaN)
true
> [NaN].findIndex(x => Number.isNaN(x))
0
> [NaN].find(x => Number.isNaN(x))
NaN

Leider gibt es keine einfache Faustregel. Wir müssen für jede Methode prüfen, wie sie mit NaN umgeht.

16.5.2 Fehlerwert: Infinity

Wann wird der Fehlerwert Infinity zurückgegeben?

Infinity wird zurückgegeben, wenn eine Zahl zu groß ist.

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

Infinity wird zurückgegeben, wenn eine Division durch Null vorliegt.

> 5 / 0
Infinity
> -5 / 0
-Infinity
16.5.2.1 Infinity als Standardwert

Infinity ist größer als alle anderen Zahlen (außer NaN) und eignet sich daher gut als Standardwert.

function findMinimum(numbers) {
  let min = Infinity;
  for (const n of numbers) {
    if (n < min) min = n;
  }
  return min;
}

assert.equal(findMinimum([5, -1, 2]), -1);
assert.equal(findMinimum([]), Infinity);
16.5.2.2 Prüfen auf Infinity

Hier sind zwei gängige Möglichkeiten, zu prüfen, ob ein Wert x Infinity ist.

const x = Infinity;

assert.equal(x === Infinity, true);
assert.equal(Number.isFinite(x), false);

  Übung: Zahlen vergleichen

exercises/numbers-math/find_max_test.mjs

16.6 Die Präzision von Zahlen: Vorsicht bei Dezimalbrüchen

Intern werden JavaScript-Gleitkommazahlen in Basis 2 dargestellt (gemäß dem IEEE 754-Standard). Das bedeutet, dass Dezimalbrüche (Basis 10) nicht immer präzise dargestellt werden können.

> 0.1 + 0.2
0.30000000000000004
> 1.3 * 3
3.9000000000000004
> 1.4 * 100000000000000
139999999999999.98

Wir müssen daher Rundungsfehler bei der Durchführung von Berechnungen in JavaScript berücksichtigen.

Lesen Sie weiter für eine Erklärung dieses Phänomens.

  Quiz: Grundlagen

Siehe Quiz-App.

16.7 (Fortgeschritten)

Alle verbleibenden Abschnitte dieses Kapitels sind fortgeschritten.

16.8 Hintergrund: Gleitkomma-Präzision

In JavaScript führen Berechnungen mit Zahlen nicht immer zu korrekten Ergebnissen – zum Beispiel

> 0.1 + 0.2
0.30000000000000004

Um zu verstehen, warum, müssen wir untersuchen, wie JavaScript Gleitkommazahlen intern darstellt. Es verwendet drei Ganzzahlen dafür, die insgesamt 64 Bit Speicherplatz beanspruchen (doppelte Genauigkeit).

Komponente Größe Ganzzahlbereich
Vorzeichen 1 Bit [0, 1]
Bruchteil 52 Bits [0, 252−1]
Exponent 11 Bits [−1023, 1024]

Die von diesen Ganzzahlen dargestellte Gleitkommazahl wird wie folgt berechnet

(–1)Vorzeichen × 0b1.Bruchteil × 2Exponent

Diese Darstellung kann keine Null kodieren, da ihre zweite Komponente (die den Bruchteil betrifft) immer eine führende 1 hat. Daher wird eine Null über den speziellen Exponenten -1023 und einen Bruchteil von 0 kodiert.

16.8.1 Eine vereinfachte Darstellung von Gleitkommazahlen

Um weitere Diskussionen zu erleichtern, vereinfachen wir die vorherige Darstellung

Die neue Darstellung funktioniert wie folgt

Mantisse × 10Exponent

Probieren wir diese Darstellung für einige Gleitkommazahlen aus.

Darstellungen mit negativen Exponenten können auch als Brüche mit positiven Exponenten in den Nennern geschrieben werden.

> 15 * (10 ** -1) === 15 / (10 ** 1)
true
> 25 * (10 ** -2) === 25 / (10 ** 2)
true

Diese Brüche helfen zu verstehen, warum es Zahlen gibt, die unsere Kodierung nicht darstellen kann.

Um unseren Ausflug abzuschließen, wechseln wir zurück zur Basis 2.

Jetzt sehen wir, warum 0.1 + 0.2 kein korrektes Ergebnis liefert: Intern kann keiner der beiden Operanden präzise dargestellt werden.

Die einzige Möglichkeit, präzise mit Dezimalbrüchen zu rechnen, ist der interne Wechsel zur Basis 10. Für viele Programmiersprachen ist die Basis 2 die Standardeinstellung und die Basis 10 eine Option. Zum Beispiel hat Java die Klasse BigDecimal und Python hat das Modul decimal. Es gibt Pläne, etwas Ähnliches zu JavaScript hinzuzufügen: der ECMAScript-Vorschlag „Decimal“.

16.9 Ganzzahlen in JavaScript

Ganzzahlen sind normale (Gleitkomma-)Zahlen ohne Dezimalbrüche.

> 1 === 1.0
true
> Number.isInteger(1.0)
true

In diesem Abschnitt betrachten wir einige Werkzeuge zur Arbeit mit diesen Pseudo-Ganzzahlen. JavaScript unterstützt auch Bigints, die echte Ganzzahlen sind.

16.9.1 Umwandlung in Ganzzahlen

Die empfohlene Methode zur Umwandlung von Zahlen in Ganzzahlen ist die Verwendung einer der Rundungsmethoden des Math-Objekts.

Weitere Informationen zur Rundung finden Sie in §17.3 „Rundung“.

16.9.2 Bereiche von Ganzzahlen in JavaScript

Dies sind wichtige Bereiche von Ganzzahlen in JavaScript.

16.9.3 Sichere Ganzzahlen

Dies ist der Bereich von Ganzzahlen, die in JavaScript sicher sind (53 Bit plus ein Vorzeichen).

[–(253)+1, 253–1]

Eine Ganzzahl ist sicher, wenn sie durch genau eine JavaScript-Zahl dargestellt wird. Da JavaScript-Zahlen als Bruchteil multipliziert mit 2 hoch einer Potenz eines Exponenten kodiert sind, können auch höhere Ganzzahlen dargestellt werden, aber dann gibt es Lücken zwischen ihnen.

Zum Beispiel (18014398509481984 ist 254)

> 18014398509481984
18014398509481984
> 18014398509481985
18014398509481984
> 18014398509481986
18014398509481984
> 18014398509481987
18014398509481988

Die folgenden Eigenschaften von Number helfen bei der Bestimmung, ob eine Ganzzahl sicher ist.

assert.equal(Number.MAX_SAFE_INTEGER, (2 ** 53) - 1);
assert.equal(Number.MIN_SAFE_INTEGER, -Number.MAX_SAFE_INTEGER);

assert.equal(Number.isSafeInteger(5), true);
assert.equal(Number.isSafeInteger('5'), false);
assert.equal(Number.isSafeInteger(5.1), false);
assert.equal(Number.isSafeInteger(Number.MAX_SAFE_INTEGER), true);
assert.equal(Number.isSafeInteger(Number.MAX_SAFE_INTEGER+1), false);

  Übung: Sichere Ganzzahlen erkennen

exercises/numbers-math/is_safe_integer_test.mjs

16.9.3.1 Sichere Berechnungen

Schauen wir uns Berechnungen mit unsicheren Ganzzahlen an.

Das folgende Ergebnis ist falsch und unsicher, obwohl beide Operanden sicher sind.

> 9007199254740990 + 3
9007199254740992

Das folgende Ergebnis ist sicher, aber falsch. Der erste Operand ist unsicher; der zweite Operand ist sicher.

> 9007199254740995 - 10
9007199254740986

Daher ist das Ergebnis eines Ausdrucks a op b korrekt, wenn und nur wenn

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

Das heißt, beide Operanden und das Ergebnis müssen sicher sein.

16.10 Bitwise-Operatoren

16.10.1 Intern arbeiten Bitwise-Operatoren mit 32-Bit-Ganzzahlen

Intern arbeiten JavaScripts Bitwise-Operatoren mit 32-Bit-Ganzzahlen. Sie produzieren ihre Ergebnisse in folgenden Schritten.

16.10.1.1 Die Typen von Operanden und Ergebnissen

Für jeden Bitwise-Operator nennt dieses Buch die Typen seiner Operanden und seines Ergebnisses. Jeder Typ ist immer einer der folgenden beiden.

Typ Beschreibung Größe Bereich
Int32 vorzeichenbehaftete 32-Bit-Ganzzahl 32 Bits inkl. Vorzeichen [−231, 231)
Uint32 vorzeichenlose 32-Bit-Ganzzahl 32 Bits [0, 232)

Unter Berücksichtigung der zuvor genannten Schritte empfehle ich, so zu tun, als würden Bitwise-Operatoren intern mit vorzeichenlosen 32-Bit-Ganzzahlen arbeiten (Schritt „Berechnung“) und dass Int32 und Uint32 nur beeinflussen, wie JavaScript-Zahlen in und aus Ganzzahlen konvertiert werden (Schritte „Eingabe“ und „Ausgabe“).

16.10.1.2 JavaScript-Zahlen als vorzeichenlose 32-Bit-Ganzzahlen anzeigen

Bei der Untersuchung der Bitwise-Operatoren ist es gelegentlich hilfreich, JavaScript-Zahlen in binärer Notation als vorzeichenlose 32-Bit-Ganzzahlen anzuzeigen. Das leistet b32() (dessen Implementierung später gezeigt wird).

assert.equal(
  b32(-1),
  '11111111111111111111111111111111');
assert.equal(
  b32(1),
  '00000000000000000000000000000001');
assert.equal(
  b32(2 ** 31),
  '10000000000000000000000000000000');

16.10.2 Bitwise Not

Tabelle 9: Der Bitwise Not-Operator.
Operation Name Typsignatur
~num Bitwise Not, Einerkomplement Int32 Int32 ES1

Der Bitwise Not-Operator (Tab. 9) invertiert jede Binärziffer seines Operanden.

> b32(~0b100)
'11111111111111111111111111111011'

Dieses sogenannte Einerkomplement ähnelt bei manchen arithmetischen Operationen einer Negation. Zum Beispiel ist die Addition einer Ganzzahl zu ihrem Einerkomplement immer -1.

> 4 + ~4
-1
> -11 + ~-11
-1

16.10.3 Binäre Bitwise-Operatoren

Tabelle 10: Binäre Bitwise-Operatoren.
Operation Name Typsignatur
num1 & num2 Bitwise And Int32 × Int32 Int32 ES1
num1 ¦ num2 Bitwise Or Int32 × Int32 Int32 ES1
num1 ^ num2 Bitwise Xor Int32 × Int32 Int32 ES1

Die binären Bitwise-Operatoren (Tab. 10) kombinieren die Bits ihrer Operanden, um ihre Ergebnisse zu erzeugen.

> (0b1010 & 0b0011).toString(2).padStart(4, '0')
'0010'
> (0b1010 | 0b0011).toString(2).padStart(4, '0')
'1011'
> (0b1010 ^ 0b0011).toString(2).padStart(4, '0')
'1001'

16.10.4 Bitwise-Shift-Operatoren

Tabelle 11: Bitwise-Shift-Operatoren.
Operation Name Typsignatur
num << count Linksshift Int32 × Uint32 Int32 ES1
num >> count Vorzeichenbehafteter Rechtsshift Int32 × Uint32 Int32 ES1
num >>> count Vorzeichenloser Rechtsshift Uint32 × Uint32 Uint32 ES1

Die Shift-Operatoren (Tab. 11) verschieben Binärziffern nach links oder rechts.

> (0b10 << 1).toString(2)
'100'

>> behält das höchste Bit bei, >>> nicht.

> b32(0b10000000000000000000000000000010 >> 1)
'11000000000000000000000000000001'
> b32(0b10000000000000000000000000000010 >>> 1)
'01000000000000000000000000000001'

16.10.5 b32(): Anzeige von vorzeichenlosen 32-Bit-Ganzzahlen in binärer Notation

Wir haben b32() nun einige Male verwendet. Der folgende Code ist eine Implementierung davon.

/**
 * Return a string representing n as a 32-bit unsigned integer,
 * in binary notation.
 */
function b32(n) {
  // >>> ensures highest bit isn’t interpreted as a sign
  return (n >>> 0).toString(2).padStart(32, '0');
}
assert.equal(
  b32(6),
  '00000000000000000000000000000110');

n >>> 0 bedeutet, dass wir n null Bits nach rechts verschieben. Daher tut der >>>-Operator prinzipiell nichts, aber er konvertiert n trotzdem in eine vorzeichenlose 32-Bit-Ganzzahl.

> 12 >>> 0
12
> -12 >>> 0
4294967284
> (2**32 + 1) >>> 0
1

16.11 Kurzübersicht: Zahlen

16.11.1 Globale Funktionen für Zahlen

JavaScript hat die folgenden vier globalen Funktionen für Zahlen.

Es ist jedoch besser, die entsprechenden Methoden von Number (Number.isFinite(), etc.) zu verwenden, da diese weniger Fallstricke haben. Sie wurden mit ES6 eingeführt und werden unten besprochen.

16.11.2 Statische Eigenschaften von Number

16.11.3 Statische Methoden von Number

16.11.4 Methoden von Number.prototype

(Number.prototype ist der Ort, an dem die Methoden von Zahlen gespeichert sind.)

16.11.5 Quellen

  Quiz: Fortgeschritten

Siehe Quiz-App.