Tutorials › Teil 2: Daten
Kapitel 10 von 16
Zahlen, Rechnen und Zufall
zeige("Summe:", 7 + 3);
zeige("Rest:", 17 % 5);
zeige("Potenz:", 2 ** 10);
zeige("Ganzzahlige Division gibt es nicht:", 7 / 2);
Die vier Grundrechenarten überraschen niemanden. Der Prozentoperator liefert den Rest einer Division, und der ist nützlicher, als er aussieht.
for (let i = 1; i <= 10; i++) {
if (i % 3 === 0) zeige(i, "ist durch drei teilbar");
}
zeige("gerade?", 14 % 2 === 0);
zeige("jeder vierte Durchgang:", [0,1,2,3,4,5,6,7].filter((n) => n % 4 === 0));
Mit % prüft man Teilbarkeit, wechselt zwischen zwei Zuständen und lässt Zähler zyklisch von vorn beginnen.
Kurzformen
let punkte = 10;
punkte += 5; zeige(punkte);
punkte -= 3; zeige(punkte);
punkte *= 2; zeige(punkte);
punkte++; zeige(punkte);
x += 5 ist dasselbe wie x = x + 5. Bei ++ gibt es eine Feinheit: Steht es hinter der Variablen, wird erst der alte Wert benutzt und dann erhöht. Davor ist es umgekehrt.
let a = 5;
zeige("a++ liefert:", a++, "| a ist jetzt:", a);
let b = 5;
zeige("++b liefert:", ++b, "| b ist jetzt:", b);
In einer Schleifenkopfzeile ist das egal. Mitten in einem Ausdruck ist es eine Quelle für Fehler, die man nur mit dem Debugger findet. Schreib die Erhöhung lieber in eine eigene Zeile.
Das Math-Objekt
zeige(Math.round(4.5), Math.round(4.4), Math.round(-4.5));
zeige(Math.floor(4.9), Math.ceil(4.1));
zeige(Math.abs(-12));
zeige(Math.min(3, 9, 1), Math.max(3, 9, 1));
zeige(Math.sqrt(144));
zeige(Math.PI);
Math.round(-4.5) liefert minus vier, nicht minus fünf. Gerundet wird immer in Richtung plus unendlich. Das ist selten wichtig, aber wenn es wichtig ist, dann richtig.
Zufall
Math.random() liefert eine Kommazahl von null bis knapp unter eins. Nie genau eins.
zeige(Math.random());
zeige(Math.random());
Daraus eine ganze Zahl in einem Bereich zu machen, ist der häufigste Anwendungsfall überhaupt:
function zufallszahl(von, bis) {
return Math.floor(Math.random() * (bis - von + 1)) + von;
}
zeige("Würfel:", zufallszahl(1, 6));
zeige("Lottozahl:", zufallszahl(1, 49));
zeige("Münze:", zufallszahl(0, 1) === 0 ? "Kopf" : "Zahl");
Das + 1 in der Klammer ist entscheidend. Ohne es kommt die obere Grenze nie vor, weil Math.floor immer abrundet. Genau dieser Fehler steckt in mehreren Skripten dieses Archivs, und man merkt ihn nur, wenn man sehr oft würfelt und sich wundert, dass die Sechs fehlt.
Ein zufälliger Eintrag aus einer Liste:
const sprueche = ["Läuft.", "Geht so.", "Morgen wieder.", "Frag später."];
const zufaellig = sprueche[Math.floor(Math.random() * sprueche.length)];
zeige(zufaellig);
const werte = new Uint32Array(3);
crypto.getRandomValues(werte);
zeige("sicher gezogen:", [...werte]);
Runden für die Anzeige
const preis = 19.994;
zeige(preis.toFixed(2));
zeige(typeof preis.toFixed(2));
zeige("wieder Zahl:", Number(preis.toFixed(2)));
toFixed liefert Text, keine Zahl. Wer damit weiterrechnet, bekommt aneinandergehängte Zeichenketten statt Summen. Kapitel 3 hat das Muster schon gezeigt.
Auf eine bestimmte Stelle runden, aber als Zahl:
function runden(zahl, stellen = 2) {
const faktor = 10 ** stellen;
return Math.round(zahl * faktor) / faktor;
}
zeige(runden(19.994));
zeige(runden(3.14159, 3));
Text zu Zahl
zeige(Number("42"));
zeige(Number("42.5"));
zeige(Number("42 Grad"));
zeige(parseInt("42 Grad"));
zeige(parseFloat("3.5 Liter"));
zeige(Number(""));
Number ist streng: Passt nicht die ganze Zeichenkette, kommt NaN. parseInt liest von vorne, solange es Ziffern findet, und hört dann auf.
Welches nimmt man? Bei Formularfeldern, in denen eine reine Zahl stehen soll, Number, weil du dann merkst, wenn Unsinn drinsteht. parseInt nur, wenn du absichtlich eine Zahl aus einem gemischten Text ziehen willst.
Die letzte Zeile ist eine Falle: Leerer Text ergibt null, nicht NaN. Bei einem leeren Eingabefeld rechnest du also plötzlich mit null weiter.
const eingabe = "";
if (eingabe.trim() === "" || Number.isNaN(Number(eingabe))) {
zeige("Bitte eine Zahl eingeben.");
} else {
zeige("Alles gut:", Number(eingabe));
}
NaN
Der Wert bedeutet „keine Zahl" und entsteht bei Rechnungen, die nicht aufgehen.
const kaputt = Number("abc");
zeige(kaputt);
zeige("typeof:", typeof kaputt);
zeige("gleich sich selbst?", kaputt === kaputt);
zeige("richtig prüfen:", Number.isNaN(kaputt));
NaN ist der einzige Wert in JavaScript, der ungleich sich selbst ist. Ein Vergleich mit === NaN ist deshalb immer falsch. Nimm Number.isNaN().
Deutsche Schreibweise
const zahl = 1234567.891;
zeige(zahl.toLocaleString("de-DE"));
zeige(zahl.toLocaleString("de-DE", { maximumFractionDigits: 0 }));
zeige((19.99).toLocaleString("de-DE", { style: "currency", currency: "EUR" }));
zeige((0.87).toLocaleString("de-DE", { style: "percent" }));
Punkt als Tausendertrennzeichen, Komma als Dezimaltrennzeichen, Euro hinter der Zahl. Das alles von Hand zusammenzubauen, ist unnötig, der Browser kann es.
Wenn du sehr viele Zahlen formatierst, leg das Format einmal an statt bei jedem Aufruf neu:
const format = new Intl.NumberFormat("de-DE", { minimumFractionDigits: 2 });
for (const p of [9.5, 19.99, 234]) zeige(format.format(p));
Auf dieser Seite passiert genau das beim Bauen der Ortsseiten. Bei viertausend Seiten mit je einem Dutzend Zahlen macht der Unterschied mehrere Sekunden aus.
Zeit ist das nächste Thema, und die ist noch eigenwilliger als Zahlen.