Testing for -0 in JavaScript

Update January 2018: In modern JavaScript engines that implement the ECMAScript 2015 specification the Object.is built-in function can be used to test for -0.

A few minutes ago I saw a tweet go by from my colleague, Dave Herman (@littlecalculist):



For background you need to know that JavaScript Number values have distinct representations for +0 and -0 . This is a characteristic of IEEE floating point numbers upon which the specification for JavaScript numbers is based. However, in most situations, JavaScript treats +0 and -0 as equivalent values. Both -0==+0 and -0===+0 evaluate to true . So how, do you distinguish them if you really need to? You have to find something in the language where they are treated differently.

Dave’s solution uses one such situation. In JavaScript, division by zero of a non-zero finite number produces either +Infinity or -Infinity , depending upon the sign of the zero divisor. +Infinity and -Infinity can be distinguished using the == or === operators so that gives us a test for for -0 .

Update: As Jeff Walden points out in a comment, Dave’s solution isn’t adequate because -0 isn’t the only divisor of 1 that produces -Infinity as its result. That problem can be solved changing the body of his function to:

return x===0 && (1/x)===-Infinity;

Are there any other ways to test for -0 . As far as I know, until recently there wasn’t. However, ECMAScript 5 added a new way to make this test. Here is what I came up with as a solution:

function isNegative0(x) { if (x!==0) return false; var obj=Object.freeze({z:-0}); try { Object.defineProperty(obj,'z',{value:x}); } catch (e) {return false}; return true; }

If you are a specification junkie you may want to stop and take a few minutes to see if you can figure out how this works. For the rest of you (that care) here is the explanation:

Line 2 is taking care of all the values that aren’t either -0 or +0 , for any other values !== will produce false and return. The rest of the function is about distinguishing the zero values. Line 3 creates an object with a single property whose value is -0 . The object is frozen, which means its properties cannot be modified. Line 5 tries to use Object.defineProperty to set the value of the frozen object’s sole property to the value of x . Because the object is frozen you would expect this to fail (and throw an error) as it is an attempt to change the value of a frozen property. However, the specification of defineProperty says it only throws if the value (or other property attributes) that is being set is actually different from the current value. And “different” is defined in a manner that distinguishes -0 and +0 . So if x is +0 this will be an attempt to change the value of the property and an exception is thrown. If x is -0 it isn’t a change and no exception is thrown. Line 6 catches the exception and returns false indicating that the argument isn’t -0 . If defineProperty didn’t throw we we fall through to line 7 and return true indicating that the argument is -0 . For the spec junkies, the key places that make this happen are [[DefineOwnProperty]] (section 8.12.9) and the SameValue algorithm (9.12).

I don’t think this satisfies Dave’s request for a more straight forward test, but it does illustrate a subtle feature of the ES5 specification. But does it actually work? This is one of those obscure specification requirements that you could easily imagine being overlooked by a busy JavaScript engine developer. I tested the above isNegative0 on pre-production versions of both FireFox 4 and Internet Explorer 9 and I was pleasantly surprised to find that they both worked for this test exactly as specified in ES5. So congratulations to both the FF and IE developers for paying attention to the details.

(And thanks to Peter van der Zee, @kuvos, for suggesting this is worth capturing in a blog post)