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)
Comments on this entry are closed.
This works in chrome 10 dev channel. I haven’t tested the other versions of chrome.
It works even with my own ES5 engine BESEN 🙂
This works on Chrome 9.0.597
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.
Jeff, that is trivially avoidable if you first check whether x == 0. It is still less code than the abomination in this post. 🙂
Why in the world would they make it so that zero could have a – or + value? Shouldn’t it just be… an absolute zero?
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.
…the loveliness of inexact numbers.
Lovelier would be giving javascript exact numbers as well as inexact!
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. 🙂 )
I might be missing something here, but why is it that the SameValue algorithm has a different behaviour than the === method?
function isReallyEqual(x, y) {
try {
Object.defineProperty( Object.freeze( { z: x } ), “z”, { value: y } );
return true;
}
catch (e)
{
return false;
}
}
x === Math.ceil(-0.5) ?
scratch that 🙂 11.9.6 does not distinguish +0 from -0.
How about a==0 && a+”===”0.0″?
return n === 0 && Math.atan(n, n) < 0
Oops… This correct variant:
return n === 0 && Math.atan2(n, n) < 0
Lets play a game 🙂
(1/[x][x]) == 1/-0
Sounds like we could use a ==== operator…
http://wiki.ecmascript.org/doku.php?id=harmony:egal
/be
Nice, I love it!
A really stupid question, why do you need to test if a number is -0? Thanks!
@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.
Thanks Tom!
Yeah! It’s fascinating, but I’m stumped for a real-world use case.