JavaScript Has Two Zeros (And One is Negative?)
JS Edition
I’ve been writing JavaScript for over a decade. I thought I understood numbers. Then someone showed me this:
-0 === 0 // true
Object.is(-0, 0) // falseWait. What?
JavaScript has two zeros. And they’re both zero. But one is negative. And they’re equal. Except when they’re not.
Let me explain this mess.
Yes, Negative Zero Exists
const negativeZero = -0;
const positiveZero = 0;
console.log(negativeZero); // 0 (looks normal)
console.log(positiveZero); // 0 (also looks normal)
console.log(negativeZero === positiveZero); // true (okay...)
console.log(Object.is(negativeZero, positiveZero)); // false (WHAT)This isn’t a JavaScript bug. This is by design. It comes from IEEE 754, the floating-point standard that JavaScript (and most languages) use for numbers.
And yes, it’s as dumb as it sounds.
How Do You Even Get -0?
Most of the time, you don’t. JavaScript hides it from you. But you can create it:
-0 // directly
Math.round(-0.1) // -0
Math.floor(-0.9) // -0
-1 * 0 // -0
0 / -1 // -0
-1 / Infinity // -0Try this in your console right now:
const result = Math.round(-0.3);
console.log(result); // Shows "0"
console.log(1 / result); // -Infinity (!!)The zero looks positive. But divide by it? Negative infinity.
Because it’s secretly -0.
The 1/x Test
This is the only reliable way to tell them apart:
function isNegativeZero(n) {
return n === 0 && 1/n === -Infinity;
}
isNegativeZero(0); // false
isNegativeZero(-0); // trueBecause apparently, in JavaScript:
1 / 0=Infinity1 / -0=-Infinity
And if that makes sense to you, congratulations, you understand IEEE 754 better than I do.
But Wait, === Says They’re Equal?
Yes. Because JavaScript wanted to be “helpful.”
0 === -0 // true
0 == -0 // trueThe === operator says they’re the same. But they’re not the same. The language is lying to you.
This is why Object.is() exists:
Object.is(0, -0) // false - tells the truth
Object.is(NaN, NaN) // true - also tells the truth
// For comparison:
NaN === NaN // false (???)So === lies about -0 and NaN. Object.is() tells the truth. But nobody uses Object.is() because typing three extra characters is too much work.
We deserve the bugs we get.
When You Actually Encounter This (Almost Never)
I’ve written JavaScript professionally since 2010. I’ve encountered meaningful -0 bugs exactly twice.
Scenario 1: Animations that reverse direction
let velocity = 0.5;
// User reverses direction
velocity = -velocity; // -0.5
// Slow down
velocity = velocity * 0.9;
velocity = velocity * 0.9;
velocity = velocity * 0.9;
// Eventually...
velocity = -0;
// Check if stopped
if (velocity === 0) {
// Stopped! But which direction were we going?
// The sign tells us: we were going backwards
}The negative zero preserves directionality at the zero crossing. It remembers you were going backwards before you stopped.
Neat! Totally impractical! But neat!
Scenario 2: Graphing negative values approaching zero
const temps = [-5, -3, -1, -0.1, -0.01, -0];
temps.map(t => {
if (Object.is(t, -0)) {
return "Just above freezing ❄️";
}
return t;
});The -0 distinguishes “approaching zero from below” vs “approaching zero from above.”
Again: neat in theory. Useless in practice.
The Meme-Worthy Parts
Stringifying -0:
String(-0) // "0"
(-0).toString() // "0"
JSON.stringify(-0) // "0"JavaScript hides it from you. Coward.
But wait:
JSON.stringify({value: -0}) // '{"value":0}'
// The -0 disappeared!
// But:
JSON.parse('{"value": -0}') // {value: -0}
// Wait it's back??JSON can parse -0 but can’t stringify it consistently. Make it make sense.
Array methods:
[-0].includes(0) // true
[-0].indexOf(0) // 0 (found it!)
[-0].find(x => Object.is(x, -0)) // -0 (but you had to work for it)includes() and indexOf() use === logic. So -0 and 0 are the same. Unless you use Object.is() in a callback. Then they’re different.
Sorting:
[0, -0].sort() // [0, -0] or [-0, 0]?The spec doesn’t define whether -0 sorts before or after 0. It’s literally undefined. Different browsers do different things.
Stable, reliable, predictable JavaScript.
Why Does This Exist?
IEEE 754 (the floating-point spec) says:
Positive zero: approaching zero from positive side
Negative zero: approaching zero from negative side
It’s about signed arithmetic. The sign preserves information about the computation that produced the zero.
For example:
-1e-324 // Smallest negative number
-1e-324 / 2 // Underflows to -0
1e-324 // Smallest positive number
1e-324 / 2 // Underflows to 0The sign tells you which direction you underflowed from.
But here’s the thing: JavaScript is a high-level language. We’re not writing signal processing algorithms or physics simulations. We’re toggling CSS classes and calling REST APIs.
We don’t need this level of numeric pedantry.
The Practical Advice
99.9% of the time: Pretend -0 doesn’t exist. Use ===. Live your life. The bugs you’ll hit are not worth the mental overhead.
0.1% of the time: If you’re doing something with:
Animation direction
Mathematical sign preservation
Physics simulations
Graphing near-zero values
Then maybe you care about -0. Use Object.is() to check. Use 1/x to detect it. Document why you’re doing this weird thing.
Never:
Try to be clever with
-0Write code that depends on
-0behaviorAssume other devs know about this
How Other Languages Handle This
Python:
-0.0 == 0.0 # True
-0.0 is 0.0 # FalseSame problem. Different syntax.
Ruby:
-0.0 == 0.0 # true
-0.0.eql?(0.0) # falseAlso the same problem.
C:
-0.0 == 0.0 // 1 (true)At least C is honest about its lies.
Rust:
-0.0 == 0.0 // true
-0.0_f64.is_sign_negative() // trueRust gives you explicit methods. Of course it does.
It’s not a JavaScript problem. It’s an IEEE 754 problem. JavaScript just makes it weirder by sometimes hiding it and sometimes not.
The Mental Model
Think of -0 like this:
It’s zero. But it remembers it used to be negative.
Like an ex you’re still friends with. Technically over. But there’s history there. And if you divide by it, you get negative infinity, which is basically the relationship drama coming back to haunt you.
(I’m sorry. I’ve been debugging too long.)
The Final Test
Pop quiz! What does this return?
function mystery(n) {
return Math.sign(n);
}
mystery(-0);Think about it.
The answer is: -0
Math.sign(-0) returns -0. Because of course it does. The sign of negative zero is negative zero. Makes perfect sense. Don’t think about it too hard.
But also:
Math.sign(0) // 0
Math.sign(-0) // -0
Math.sign(-1) // -1
Math.sign(1) // 1So Math.sign() is one of the few JavaScript functions that preserves the negative zero. Which is either pedantically correct or deeply cursed, depending on your perspective.
My Honest Take
I love JavaScript. I really do. I’ve built my career on it. But -0 is the language showing off how much cursed knowledge it inherited from IEEE 754.
Could JavaScript have not implemented negative zero? No. It’s in the floating-point spec. The language didn’t have a choice.
Could JavaScript be consistent about negative zero? Maybe hide it everywhere or expose it everywhere? Yeah. But instead we got “sometimes visible, sometimes hidden, always weird.”
The lesson: Computers represent numbers in ways that are deeply stupid. Floating point is a lie. Integers past Number.MAX_SAFE_INTEGER are a lie. And zero can be negative.
Welcome to programming.
Try it yourself:
Open your browser console and run:
const test = -0;
console.log(test); // 0
console.log(1/test); // -Infinity
console.log(Object.is(test, 0)); // false
console.log(test === 0); // true
// Now you know the dark secretYou can never unknow this. Sorry.
Other JavaScript number crimes:
0.1 + 0.2 !== 0.3Number.MAX_VALUE + 1 === Number.MAX_VALUEMath.max() < Math.min()typeof NaN === "number"
The numbers aren’t okay.
What JavaScript quirk keeps you up at night? Hit reply.


