+-0, NaN and Object.is in JavaScript

3 min read

This post is part of my Today I learned series in which I share all my learnings regarding web development.

The last few days I was in Cluj-Napoca where I spoke at the excellent JSHeroes conference. In the talk "V8 Internals for JS Developers" (the recording is from a different event though) of Mathias Bynens I saw a very interesting code snippet.

Object.is(-0, +0);

This single line is fascinating in two ways – let's have a look at it.

# The two existent zeros in JavaScript

The first fact is that numbers in JavaScript are following the IEEE Standard for Floating-Point Arithmetic. This standard is available in several variants, and JavaScript uses "Double precision" also called "binary64" based on 64 bits.

IEEE 754 defines that a sign, a significant, and an exponent to describe each finite number. To understand how this works it may take some time but the important fact is that there is one bit (the sign bit) in JavaScript numbers which defines if a number is positive or negative which means that 0 can be negative, too.

const posNumber = 1;
const negNumber = -1;
const posZero = +0;
const negZero = -0;

My first reaction to the discovery of negative zeros was that I surely don't have these in my code, but well... when I round -0.23 I'll end up with a negative zero as well which makes it more likely that a negative zero appears in my JavaScript, too.

Math.round(-0.23); // -0

It becomes interesting when you want to compare positive and negative zeros though because they're treated the same.

-0 === +0 // true

AbdulFattah Popoola wrote a nice article and there is even a section in the "You don't know JavaScript" series on positive and negative zeros going more into more details if you're interested.

Sidenote: you can differentiate -0 and 0 using division and the resulting Infinity.

1 / -0 === -Infinity    // true 
1 / 0 === Infinity      // true
-Infinity !== Infinity  // true

# Object.is – comparing without quirks?

So, the strict comparison with === didn't catch the fact that the two zeros are not the same. You may know that NaN is also not equal to NaN either.

NaN === NaN // false

// you can use Number.isNaN as an alternative
Number.isNaN(NaN) // true

These occasions are when Object.is could come into play. In most cases, it behaves the same as === but it includes some minor "improvements" which make things a bit more logical.

Object.is(-0, 0);    // false
Object.is(NaN, NaN); // true

The downside of this is that not everybody is aware of the existence of -0 which means that for the rounding of -0.23 a differentiation between 0 and -0 could lead to hard to spot bugs. That's probably why it's usually ignored in JavaScript.

I saw Object.is for the first time in Mathias' slides and it doesn't seem to be used that often.

One question that came to mind immediately was if it Object.is is as fast as ===. I created a quick JSPerf to see how Object.is performs in comparison to ===. In Safari and Firefox Object.is seems to be significantly slower than === whereas in Chrome it's more or less the same. That's very interesting!

If anyone has comments on the performance test, please let me know. Browser internals are extremely complicated, and sometimes an optimization takes place in a test which then invalidates the whole thing.

I'd also love to hear if you use Object.is in your source code! :)

# Additional resources

Load time