JavaScript: Wie rundet man auf höchstens 2 Nachkommastellen?
Die beiden Antworten, die alle geben, sind +x.toFixed(2) und
Math.round(x * 100) / 100. Meist werden sie als dasselbe dargestellt. Über
100.000 Werte unterscheiden sie sich bei 3.435 davon.
Warum überhaupt etwas falsch ist
0.1 + 0.2 : 0.30000000000000004
=== 0.3 : false
1.005 exactly : 1.0049999999999998934
Ein Double
speichert Binärbrüche, und 1.005 ist keiner. In der Variablen steht tatsächlich
1.0049999999999998934 — unterhalb der Hälfte. Alles Weitere folgt aus dieser
einen Zeile.
toFixed
(1.005 ).toFixed(2) = 1.00
(1.45 ).toFixed(2) = 1.45
(1.55 ).toFixed(2) = 1.55
(2.675 ).toFixed(2) = 2.67
(10.235).toFixed(2) = 10.23
toFixed
liefert für 1.005 ein 1.00, und das wird etwa einmal pro Woche als Fehler
gemeldet. Es ist keiner: 1.005 liegt wirklich unter der Hälfte, und toFixed
rundet den Wert, der da ist, nicht die Dezimalzahl, die du getippt hast.
Zurück kommt außerdem ein String:
typeof (1.5).toFixed(2) : string "1.50"
"1.50" + 1 : 1.501
+(1.5).toFixed(2) : 1.5 number
Addieren hängt an. Das führende + konvertiert zurück und wirft dabei die
angehängte Null weg — genau das “falls nötig” aus der Frage.
Math.round(x * 100) / 100
r(1.005) = 1 toFixed = 1.00
r(1.255) = 1.25 toFixed = 1.25
r(1.345) = 1.35 toFixed = 1.34
r(2.675) = 2.68 toFixed = 2.67
Bei drei von vier verschiedene Antworten. Über einen Bereich:
of 100000 values, they differ on 3435
0.015 round=0.02 toFixed=0.01
0.045 round=0.05 toFixed=0.04
0.155 round=0.16 toFixed=0.15
3,4 Prozent. Das ist keine Rundungs-Kuriosität, das ist eine Anzahl von Rechnungen.
Das Multiplizieren ist auch nicht umsonst:
Math.round(1.0000000000000001 * 100)/100 : 1
x * 100 ist selbst eine Fließkomma-Operation, der Trick fügt also einen
zweiten Fehler hinzu, bevor er den ersten korrigiert.
Die Fixes, die nichts fixen
1.005 : eps = 1.01 expo = 1.01 toFixed = 1.00
2.675 : eps = 2.68 expo = 2.68 toFixed = 2.67
Vorher Number.EPSILON addieren oder über "1.005e+2" als String gehen: beides
liefert 1.01. Das sieht nach einer Lösung aus, und der Grund, warum es keine
ist:
1.0049999999999999 === 1.005 : true
Es gibt keinen anderen Wert, den man retten könnte. Beide Schreibweisen ergeben dasselbe Double, und dieses Double liegt unter der Hälfte. Die Tricks holen keine verlorene Stelle zurück — sie schieben eine korrekt gespeicherte Zahl nach oben. In den Beispielen, die du getestet hast, stimmen sie mit deiner Intuition überein, anderswo nicht.
Das, was mich überrascht hat
1.005 toFixed=1.00 Intl=1.01
2.675 toFixed=2.67 Intl=2.68
1.345 toFixed=1.34 Intl=1.35
Zwei eingebaute Funktionen, dieselbe Eingabe, verschiedene Ausgabe. Keine davon
ist kaputt:
toFixed
rundet den exakten Binärwert,
Intl.NumberFormat
rundet die Dezimalzahl, die es gleich ausgibt, kaufmännisch von der Null weg.
Was du dem Nutzer zeigst und was du speicherst, können sich also in der letzten
Stelle widersprechen, solange du nicht eines von beiden für beides nimmst.
Für die Anzeige ist Intl ohnehin das bessere Werkzeug:
de-DE : 1.234,50 | 1,01 | 2,68
en-US : 1,234.50
Dezimalkomma und Tausendertrennzeichen, die dir keine Rundungshilfe liefert.
Bei Geld gar nicht runden
stored as an integer number of cents : 1005
displayed : 10.05
0.1 + 0.2 in cents : 0.3
Halte die kleinste Einheit als Ganzzahl und teile erst bei der Ausgabe.
Ganzzahlen sind bis
Number.MAX_SAFE_INTEGER
exakt, also bis 9007199254740991 — rund 90 Billionen Cent. Die Rundungsfrage
verschwindet, statt beantwortet zu werden.
Hinweis zu Netcup (Werbung)
Der deutsche Hoster Netcup bietet unter anderem günstige und zugleich leistungsstarke Webhosting Pakete, KVM-basierte Root Server und dezidierte Server an. Mit unseren Gutscheincodes kannst du noch mehr Geld sparen (6€ bei deiner ersten Bestellung, 30% Rabatt auf alle KVM-basierten Root Server, ...).