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