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, ...).