The Problem

You want to round a number with n decimal places to a number with only 2 decimal places. And maybe you want to do it only if necessary, so when you have a number with less than 2 decimal places, you don’t want to add extra zeros.

Example Input:

5
5.5
5.448

Output:

5
5.5
5.45

The easiest and most efficient way of rounding a number to 2 decimal places is to use scaling:

Math.round((n + Number.EPSILON) * 100) / 100

// Example
Math.round((8.765 + Number.EPSILON) * 100) / 100 // Outputs: 8.77

Why use Number.EPSILON you ask? Just calculating Math.round(8.765 * 100) / 100 will return the correct result, no?
That is correct, but only for numbers that can be represented accurately using floating points.

Let’s take the number 1.255 as a counter-example. Rounding 1.255 to 2 decimals places should give us 1.26.

let n = 1.255;
n * 100 // Outputs: 125.49999999999999

// If we round the above number, we'll get the wrong result
Math.round(n * 100) / 100 // Outputs: 1.25

If you’re thinking “What on earth did I just read?”. My friend, welcome to the world of floating point imprecision. The number 1.255 is only one out of many fractional numbers that are impossible to represent accurately using binary bits. Instead, it’s stored in memory as the closest number that can be accurately represented which is 1.25499999999999. This is why when we try to round it using the method above, it rounds down to 1.25 instead of 1.26.

To solve this problem, we add Number.EPSILON to our number, which is an infinitely small value that will help us compensate for the imprecision of floating numbers:

let n = 1.255;
n * 100 // Outputs: 125.49999999999999
(n + Number.EPSILON) * 100 // Outputs: 125.50000000000001

Math.round((n + Number.EPSILON) * 100) / 100 // Outputs: 1.26

If you’re interested, you can read more about floating point imprecision in this article.

Float.toFixed()

You can use the builtin function toFixed() to round numbers, this function takes as an argument the number of decimal places you want. Don’t forget, you’ll still need to use Number.EPSILON.

let n = 1.255;
(n + Number.EPSILON).toFixed(2); // Outputs: "1.26"

There are 2 catches to consider when using toFixed():

  1. The return value is a string. So if you intend to do further calculations with the result, you have to cast it back to a number.
let n = 56.756;
n = (n + Number.EPSILON).toFixed(2); // "56.76"
n + 1 // "56.761" (Ouch!)

n = parseFloat(n);
n + 1 // 57.76
  1. The return value will include the full number of decimal points given as an argument, even if not necessary. To remove the excess, you also have to cast it back to a number.
let n = 10.5;
n = (n + Number.EPSILON).toFixed(2); // "10.50"
n = parseFloat(n); // 10.5

Math.floor() and Math.ceil()

The Math.floor() and Math.ceil() functions return the largest and smallest integer less than or equal to a given number, respectively. So we can use scaling to round a number up or down to 2 decimal places, just like we do with Math.round().

Here’s how you can use Math.floor() to round down:

let n = 5.6789;
let roundedN = Math.floor(n * 100) / 100; // Output: 5.67

And here’s how you can use Math.ceil() to round up:

let num = 5.6789;
let roundedNum = Math.ceil(num * 100) / 100; // Output: 5.68

Using one of these 2 functions, you don’t have to use Number.EPSILON because you are explicitly instructing JavaScript to round up or down. Floating point imprecision will have no effect in this case.

Similar Posts