Alxblsk.com Logo
RE•DONE
Blog by Aliaksei Belski

Javascript and Numbers

Published: May 27th, 2020javascriptieee-754

Javascript and Numbers

Inaccurate calculations

My first acquaintance with weird number calculation behavior had happened back in 2012 when I realized that getting a sum of two fractions did not return an expected result. It was similar to

const sum = 0.1 + 0.2; // 0.30000000000000004

and returned an inaccurate result. This is how I met IEEE 754 specification for the first time, and I got that not every decimal can be precisely represented (there is a good StackOverflow answer).

This is a pretty common error when developers start counting money and operate with currencies that have a decimal part (frankly, most of them). But there is a solution.

Strive to work with integers.

May sound naive, but it may save hours of debugging in a big application. Just compare two solutions: the first one calculates decimals as is…

const purchase1 = 3.57; // dollars
const purchase2 = 99.99; // dollars
const result = purchase1 + purchase2; // 103.55999999999999

…and second one has exact precision for UI.

const purchase1 = 357; // cents
const purchase2 = 9999; // cents
const precision = 2;
const result = (purchase1 + purchase2) / 10 ** precision; // 103.56

Thankfully, Javascript’s number is big enough to store such values as purchases safely (Number.MAX_SAFE_INTEGER equals to 9007199254740991).

As a second option, think of your rounding tactic to get precise and predictable results.

Corner Cases

There are several more unusual values defined by the standard:

  • 0/-0
  • Infinity/-Infinity
  • NaN (Not a Number). Yes, this value is also a Number.

±Zero

In reality, it’s hard to get a negative zero. In most calculations such as 3 - 3 it’ll be a still regular 0. However, we can still get a negative one by dealing with Infinity:

const negativeZero = 0 / -Infinity; // -0

But even in this case, you likely won’t be affected by negative zero, as comparison +0 === -0 still returns true. However, ES2015 introduced Object.is(), which helps us to determine values like -0 :

const isNegativeZero = Object.is(0, -0); // false

Not a Number

Talking about NaN. Despite the fact that it is considered as a Number, in JS there are plenty of ways to determine this exception, like:

console.log(NaN !== NaN); // true
console.log(isNaN(NaN)); // true
console.log(Number.isNaN(NaN)); // true
console.log(Object.is(NaN, NaN)); // true

All of them are good except (#2). I don’t recommend using isNaN(NaN) because results of it may be unexpected (coercion inside makes it not precise and unreliable).

console.log(isNaN("🥕")); // also true and makes sense, it's a carrot!

±Infinity

In reality, it is rather simple to get an Infinity and ruin your calculations. The most simple case is division by zero. Don’t think your try/catch statement will help you, because it is not a case for IEEE 754. You’ll get a surprise instead:

const fancyCalculation = 1 / 0; // Infinity!
const anotherCalculation = 0 / 0; // Guess what? NaN!

Thankfully, together with ECMAScript 2015 we’ve got a good tool to exclude corner cases after a calculation, and it’s called Number.isFinite()(like isFinite but no coercion so more precise results).

console.log(Number.isFinite(NaN)); // false
console.log(Number.isFinite(Infinity)); // false

You should be safe enough now!


IEEE 754 specification may be misleading, and its predefined behavior casts a shadow on Javascript. But who said development is simple? Just handle corner cases, and write tests. Having one type for numbers (in opposite to such language as Java) is still good enough for a language, which was developed in ten days.