Testing for -0 in JavaScript

February 16, 2011

in JavaScript,Web Standards

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)

{ 24 comments }

Elliott February 16, 2011 at 3:01 pm

This works in chrome 10 dev channel. I haven’t tested the other versions of chrome.

Benjamin Rosseaux February 16, 2011 at 3:43 pm

It works even with my own ES5 engine BESEN :-)

Jesse February 16, 2011 at 6:36 pm

This works on Chrome 9.0.597

Jeff Walden February 16, 2011 at 8:33 pm

For what it’s worth, Dave’s test doesn’t actually work: more numbers than -0 divide 1 to produce -Infinity. -Math.pow(2, -1074) as the smallest non-zero denormal is the simplest counterexample.

Zing February 16, 2011 at 8:40 pm

Jeff, that is trivially avoidable if you first check whether x == 0. It is still less code than the abomination in this post. :)

Tom Chapin February 16, 2011 at 9:05 pm

Why in the world would they make it so that zero could have a – or + value? Shouldn’t it just be… an absolute zero?

Jeff Walden February 17, 2011 at 1:02 am

Permitting both +0 and -0 allows for saner behavior for mathematical algorithms with a discontinuity for the input zero: negative values close to zero can have approaching-zero-from-the-left behavior, and positive values close to zero can have approaching-zero-from-the-right behavior.

+/-0 also allows for the “expected” sign of computations to be preserved when a fractional value becomes too small in magnitude to be represented as anything but zero. Instead of -1/2, -1/4, -1/8… decaying to +0 as it would if there were only one zero, it can decay to -0 and still preserve proper signed behavior, rather than going completely weird due to the sign change.

For another case, having both +0 and -0 preserves the equivalence 1 / (1 / x) == x when x is positive or negative infinity. This might also apply to other extremely large or extremely small (in magnitude) values for x, but it’s been awhile since I really, really tried to work through exactly what the traditional (sign * base ** exponent) floating point representation allows and doesn’t allow, so I hesitate to say anything more conclusive.

For more on floating point representation and concerns, I can’t recommend the classic paper What Every Computer Scientist Should Know About Floating-Point Arithmetic (PDF) any more highly. It has a whole section addressing many of these sorts of questions specifically for signed zero values. And it has much more on many other interesting and genuinely useful applications of floating point tricks and innovations.

Patrick Logan March 8, 2011 at 8:57 pm

…the loveliness of inexact numbers.

Lovelier would be giving javascript exact numbers as well as inexact!

Jeff Walden February 16, 2011 at 9:42 pm

Zing, quite true, as I proposed in my own update in the Twittersphere. But I don’t think you can get any simpler than two ops like that. (And certainly what the original post demonstrates is emphatically not simpler. :-) )

Remon Oldenbeuving February 17, 2011 at 1:24 am

I might be missing something here, but why is it that the SameValue algorithm has a different behaviour than the === method?

Neil Rashbrook February 17, 2011 at 2:06 am

function isReallyEqual(x, y) {
try {
Object.defineProperty( Object.freeze( { z: x } ), “z”, { value: y } );
return true;
}
catch (e)
{
return false;
}
}

sof February 17, 2011 at 3:52 am

x === Math.ceil(-0.5) ?

sof February 17, 2011 at 4:05 am

scratch that :) 11.9.6 does not distinguish +0 from -0.

Ben February 17, 2011 at 10:45 am

How about a==0 && a+”===”0.0″?

Bolk February 17, 2011 at 12:26 pm

return n === 0 && Math.atan(n, n) < 0

Bolk February 17, 2011 at 12:27 pm

Oops… This correct variant:

return n === 0 && Math.atan2(n, n) < 0

Tom Schuster February 17, 2011 at 1:43 pm

Lets play a game :)
(1/[x][x]) == 1/-0

Greg Perkins February 17, 2011 at 8:48 pm

Sounds like we could use a ==== operator…

Brendan Eich February 22, 2011 at 11:27 am
Neil Rashbrook February 25, 2011 at 2:19 am

Nice, I love it!

Edward February 18, 2011 at 7:29 pm

A really stupid question, why do you need to test if a number is -0? Thanks!

Tom Longson February 23, 2011 at 2:24 pm

@edward, I don’t think that’s a stupid question. This has never came up for me personally, but I could see it being relevant if you were to build a calculator in JavaScript. I’m curious what other real world applications there might be too.

Edward February 23, 2011 at 2:53 pm

Thanks Tom!

Joss Crowcroft February 26, 2011 at 9:40 am

Yeah! It’s fascinating, but I’m stumped for a real-world use case.

{ 1 trackback }

Previous post:

Next post: