How to round numbers in JavaScript properly

JavaScript as many other programming languages has real difficulties when it comes to rounding numbers on edge cases for floating numbers.

TL;DR;

Proper, elegant solution to round numbers in JavaScript using Number.EPSILON (MDN link) correction πŸ€“:


const round = (num = 0, fractions = 0) => { 
  const k = Math.pow(10, fractions); 
  const n = (num * k) * (1 + Number.EPSILON); 
  return Math.round(n) / k; 
}

With this util you can properly handle edge cases like these:

// custom round works properly
console.log(round(23.435, 1) === 23.4);// true
console.log(round(4.475, 2) === 4.48); // true
console.log(round(1.255, 2) === 1.26); // true
console.log(round(1.005, 2) === 1.01); // true
console.log(round(5.015, 2) === 5.02); // true

// native method fail to round
console.log(+23.435.toFixed(1) === 23.4);// true
console.log(+4.475.toFixed(2) === 4.48); // false
console.log(+1.255.toFixed(2) === 1.26); // false
console.log(+1.005.toFixed(2) === 1.01); // false
console.log(+5.015.toFixed(2) === 5.02); // false

View full code on TypeScript playground demo

Why this needed?

Native method to round floating numbers in JavaScript is .toFixed which causes bugs on edge cases due to Round-off error (read on wikipedia).

The most prominent example of issue is when you add two simple number 0.1 and 0.2:

0.1 + 0.2 === 0.30000000000000004

This example is so popular that there is even a website dedicated to showcase this problem - https://0.30000000000000004.com/

Why this can be a problem?

Miscalculated numbers may have different bad impact:

  • ranging from bad "out of boundaries" rendering of values on frontend (UI bugs)
  • to causing failed comparisons of numbers which may trigger wrong conditional branches to run actually unintended code (functional bugs)
  • and potentially cause invalid financial transactions processing with wrong amount of money distributed between participants

Some effects may be noticed immediately, for example hash functions produce completely different results even when a single character out of long text is different (which is in our case may be a different small fraction of a number).

While other effects are not that obvious but may cause eventually wrong results due to accumulation of slight errors over time (imagine slightly different number multiplied millions of time in some algorithms).

How to handle advanced cases

For very complex and precise calculations with floating numbers you may need to use npm package like this - https://mikemcl.github.io/decimal.js/