How to round to at most 2 decimal places in JavaScript
The two answers everyone gives are +x.toFixed(2) and
Math.round(x * 100) / 100. They are usually presented as the same thing.
Over 100,000 values they differ on 3,435 of them.
Why anything is wrong at all
0.1 + 0.2 : 0.30000000000000004
=== 0.3 : false
1.005 exactly : 1.0049999999999998934
A double
stores binary fractions, and 1.005 is not one. What is actually in the variable
is 1.0049999999999998934 — below the halfway point. Everything below follows
from that one line.
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
gives 1.00 for 1.005 and this is reported as a bug about once a week. It is
not one: 1.005 is genuinely below the midpoint, and toFixed rounds the value
that is there rather than the decimal you typed.
It also returns a string:
typeof (1.5).toFixed(2) : string "1.50"
"1.50" + 1 : 1.501
+(1.5).toFixed(2) : 1.5 number
Adding to it concatenates. The leading + converts back, and doing so drops
the trailing zero — which is what the “if necessary” in the question asks for.
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
Different answers on three of four. Over a range:
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 %. That is not a rounding curiosity, it is a number of invoices.
Multiplying is not free either:
Math.round(1.0000000000000001 * 100)/100 : 1
x * 100 is itself a floating-point operation, so the trick introduces a second
error before it corrects the first.
The fixes that do not fix it
1.005 : eps = 1.01 expo = 1.01 toFixed = 1.00
2.675 : eps = 2.68 expo = 2.68 toFixed = 2.67
Adding Number.EPSILON first, or round-tripping through "1.005e+2", both
produce 1.01. That looks like a fix, and the reason it is not is:
1.0049999999999999 === 1.005 : true
There is no separate value to preserve. Both spellings parse to the same double, and that double is below the midpoint. These tricks do not recover a lost digit — they push a correctly-stored number up. They agree with your intuition on the examples you tested and disagree with it elsewhere.
The one that surprised me
1.005 toFixed=1.00 Intl=1.01
2.675 toFixed=2.67 Intl=2.68
1.345 toFixed=1.34 Intl=1.35
Two built-ins, same input, different output. Neither is broken:
toFixed
rounds the exact binary value, while
Intl.NumberFormat
rounds the decimal it is about to print, half away from zero. So the thing you
show a user and the thing you store can disagree on the last digit unless you
pick one and use it for both.
For display, Intl is the better tool anyway:
de-DE : 1.234,50 | 1,01 | 2,68
en-US : 1,234.50
Decimal comma and thousands separator, which no rounding helper gives you.
For money, do not round
stored as an integer number of cents : 1005
displayed : 10.05
0.1 + 0.2 in cents : 0.3
Hold the smallest unit as an integer and divide only when printing. Integers
are exact up to
Number.MAX_SAFE_INTEGER,
which is 9007199254740991 — about 90 trillion cents. The rounding question
disappears rather than being answered.
About Netcup (advertisement)
The German host Netcup offers, among other things, affordable and powerful web hosting packages, KVM-based root servers and dedicated servers. With our voucher codes you can save even more (6€ off your first order, 30% off all KVM-based root servers, ...).