JavaScript Has Two Zeros (And One is Negative?)

JS Edition

March 16, 2026

I’ve been writing JavaScript for over a decade. I thought I understood numbers. Then someone showed me this:

-0 === 0  // true\nObject.is(-0, 0)  // false

Wait. 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;\nconst positiveZero = 0;\n\nconsole.log(negativeZero);  // 0 (looks normal)\nconsole.log(positiveZero);  // 0 (also looks normal)\n\nconsole.log(negativeZero === positiveZero);  // true (okay...)\nconsole.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\nMath.round(-0.1)  // -0\nMath.floor(-0.9)  // -0\n-1 * 0  // -0\n0 / -1  // -0\n-1 / Infinity  // -0

Try this in your console right now:

const result = Math.round(-0.3);\nconsole.log(result);  // Shows "0"\nconsole.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) {\n  return n === 0 && 1/n === -Infinity;\n}\n\nisNegativeZero(0);   // false\nisNegativeZero(-0);  // true

Because apparently, in JavaScript:

  • 1 / 0 = Infinity

  • 1 / -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\n0 == -0   // true

The === 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\nObject.is(NaN, NaN)  // true - also tells the truth\n\n// For comparison:\nNaN === 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;\n\n// User reverses direction\nvelocity = -velocity;  // -0.5\n\n// Slow down\nvelocity = velocity * 0.9;\nvelocity = velocity * 0.9;\nvelocity = velocity * 0.9;\n\n// Eventually...\nvelocity = -0;\n\n// Check if stopped\nif (velocity === 0) {\n  // Stopped! But which direction were we going?\n  // The sign tells us: we were going backwards\n}

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];\n\ntemps.map(t => {\n  if (Object.is(t, -0)) {\n    return "Just above freezing ❄️";\n  }\n  return t;\n});

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"\n(-0).toString()  // "0"\nJSON.stringify(-0)  // "0"

JavaScript hides it from you. Coward.

But wait:

JSON.stringify({value: -0})  // '{"value":0}'\n// The -0 disappeared!\n\n// But:\nJSON.parse('{"value": -0}')  // {value: -0}\n// Wait it's back??

JSON can parse -0 but can’t stringify it consistently. Make it make sense.

Array methods:

[-0].includes(0)  // true\n[-0].indexOf(0)   // 0 (found it!)\n[-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\n-1e-324 / 2  // Underflows to -0\n\n1e-324  // Smallest positive number  \n1e-324 / 2  // Underflows to 0

The 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 -0

  • Write code that depends on -0 behavior

  • Assume other devs know about this

How Other Languages Handle This

Python:

-0.0 == 0.0  # True\n-0.0 is 0.0  # False

Same problem. Different syntax.

Ruby:

-0.0 == 0.0  # true\n-0.0.eql?(0.0)  # false

Also the same problem.

C:

-0.0 == 0.0  // 1 (true)

At least C is honest about its lies.

Rust:

-0.0 == 0.0  // true\n-0.0_f64.is_sign_negative()  // true

Rust 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) {\n  return Math.sign(n);\n}\n\nmystery(-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\nMath.sign(-0)  // -0\nMath.sign(-1)  // -1\nMath.sign(1)  // 1

So 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;\nconsole.log(test);  // 0\nconsole.log(1/test);  // -Infinity\nconsole.log(Object.is(test, 0));  // false\nconsole.log(test === 0);  // true\n\n// Now you know the dark secret

You can never unknow this. Sorry.


Other JavaScript number crimes:

  • 0.1 + 0.2 !== 0.3

  • Number.MAX_VALUE + 1 === Number.MAX_VALUE

  • Math.max() < Math.min()

  • typeof NaN === "number"

The numbers aren’t okay.


What JavaScript quirk keeps you up at night? Hit reply.

Thanks for reading Under The Hood! Subscribe for free to receive new posts and support my work.

Leave a comment