Black lives matter

(Ad, please don’t block)

JavaScript has two zeros: −0 and +0. This post explains why that is and where it matters in practice.

Binary 1010 is decimal −2Naturally, that means that there will be two zeros: 1000 (−0) and 0000 (+0).

Binary 0010 is decimal +2

In JavaScript, all numbers are floating point numbers, encoded in double precision according to the IEEE 754 standard for floating point arithmetic. That standard handles the sign in a manner similar to sign-and-magnitude encoding for integers and therefore also has a *signed zero*. Whenever you represent a number digitally, it can become so small that it is indistinguishable from 0, because the encoding is not precise enough to represent the difference. Then a signed zero allows you to record “from which direction” you approached zero, what sign the number had before it was considered zero. Wikipedia nicely sums up the pros and cons of signed zeros:

It is claimed that the inclusion of signed zero in IEEE 754 makes it much easier to achieve numerical accuracy in some critical problems, in particular when computing with complex elementary functions. On the other hand, the concept of signed zero runs contrary to the general assumption made in most mathematical fields (and in most mathematics courses) that negative zero is the same thing as zero. Representations that allow negative zero can be a source of errors in programs, as software developers do not realize (or may forget) that, while the two zero representations behave as equal under numeric comparisons, they are different bit patterns and yield different results in some operations.JavaScript goes to some lengths to hide the fact that there are two zeros.

> -0 0The reason is that the standard

> (-0).toString() '0' > (+0).toString() '0'The illusion of a single zero is also perpetrated by the equals operators. Even strict equality considers both zeros the same, making it very hard to tell them apart (in the rare case that you want to).

> +0 === -0 trueThe same holds for the less-than and greater-than operators – they consider both zeros equal.

> -0 < +0 false > +0 < -0 false

function signed(x) { if (x === 0) { // isNegativeZero() will be shown later return isNegativeZero(x) ? "-0" : "+0"; } else { // Otherwise, fall back to the default // We don’t use x.toString() so that x can be null or undefined return Number.prototype.toString.call(x); } }

The sum of two negative zeros is −0. The sum of two positive zeros, or of two zeros of opposite sign, is +0.For example:

> signed(-0 + -0) '-0' > signed(-0 + +0) '+0'This doesn’t give you a way to distinguish the two zeros, because what comes out is as difficult to distinguish as what goes in.

> signed(+0 * -5) '-0' > signed(-0 * -5) '+0'Multiplying an infinity with a zero results in

> -Infinity * +0 NaN

> 5 / +0 Infinity > 5 / -0 -Infinity > -Infinity / +0 -InfinityNote that

> -Infinity === Infinity falseDividing a zero by a zero results in

> 0 / 0 NaN > +0 / -0 NaN

pow(+0, y<0) → +∞Interaction:

pow(−0, odd y<0) → −∞

pow(−0, even y<0) → +∞

> Math.pow(+0, -1) Infinity > Math.pow(-0, -1) -Infinity

atan2(+0, +0) → +0Hence, there are several ways to determine the sign of a zero. For example:

atan2(+0, −0) → +π

atan2(−0, +0) → −0

atan2(−0, −0) → −π

atan2(+0, x<0) → +π

atan2(−0, x<0) → −π

> Math.atan2(-0, -1) -3.141592653589793 > Math.atan2(+0, -1) 3.141592653589793

atan2(y>0, +∞) → +0Therefore:

atan2(y<0, +∞) → −0

> signed(Math.atan2(-1, Infinity)) '-0'

> signed(Math.round(-0.1)) '-0'Here we have the effect that we talked about at the beginning: The sign of the zero records the sign of the value before rounding, “from which side” we approached 0.

function isNegativeZero(x) { return x === 0 && (1/x < 0); }The above sections showed several other options. One original solution comes from Allen Wirfs-Brock. Here is a slightly modified version of it:

function isNegativeZero(x) { if (x !== 0) return false; var obj = {}; Object.defineProperty(obj, 'z', { value: -0, configurable: false }); try { // Is x different from z’s previous value? Then throw exception. Object.defineProperty(obj, 'z', { value: x }); } catch (e) { return false }; return true; }Explanation: In general, you cannot redefine a non-configurable property – an exception will be thrown. For example:

TypeError: Cannot redefine property: zHowever, JavaScript will ignore your attempt if you use the same value as the existing one. In this case, whether a value is the same is not determined via