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/