Bare Metal JavaScript: The JavaScript Virtual Machine

Negative Numbers

Miško Hevery

Miško Hevery

Qwik Creator (Previously Angular)
Bare Metal JavaScript: The JavaScript Virtual Machine

Check out a free preview of the full Bare Metal JavaScript: The JavaScript Virtual Machine course

The "Negative Numbers" Lesson is part of the full, Bare Metal JavaScript: The JavaScript Virtual Machine course featured in this preview video. Here's what you'd learn in this lesson:

Miško explores a JavaScript behavior where using negative zero (-0) can lead to slower performance compared to performing zero minus X (0 - X). This phenomenon arises from how JavaScript handles negative zero as a floating-point number, which can disrupt compiler optimizations when accessing properties or arrays with integers, resulting in performance differences between browsers.


Transcript from the "Negative Numbers" Lesson

>> Okay, here's a fun one. I don't have a benchmark to show you but I think it's fun to talk about for a second. And that is, why in the world is- x slower than 0- x? Anybody who wants to take a crack at this, I think this is a pretty fun one.

>> Is-
>> Go ahead.
>> Is- x a multiplication?
>> No.
>> No, okay.
>> That was a good one.
>> Does it have to do with coercion?
>> Sort of, yes. Yes, you're on track. So, you were talking about two's complement, right? Negative numbers.
>> No, I was thinking that, is it more steps in the assembly code to change it from positive?

>> No, not even that. Not even that. There is no such thing as- 0. Well, actually, let's do a sane thing. There's no such thing as- 0, there's only 0. Except, JavaScript is not sane.
>> [LAUGH]
>> JavaScript understands 0, and it also understands- 0. It's just wrote- 0, right?

This is insane, 0- 0? 0, but- 0, -0. Okay, what in the world's going on here, right? To understand this is there is no way for the CPU to encode- 0. Negative 0 doesn't exist because two's complement of zero, remember two's complement is you flip all the bits.

So you get one, one, one everywhere, and then you add one to it. And when you add one to one, one, one everywhere, it overflows and becomes zero again. So negation of a zero, or two's complement of zero is zero, right? And that's fine, because in math there's no such thing as- 0 unless you do limits, right?

And so in JavaScript is different. And so for some reason, it encodes- 0. Now, if you write this thing, 0- x, then what you're saying is subtract the value from 0. If you say this, then you're actually saying, give me the negative of this number, which for the most part is a number, except when it's 0, then it's- 0, which cannot be encoded into as a integer.

So what does JavaScript do? It encodes us as eee754. eee754 is how we do floating point numbers. Turns out floating point numbers can have- 0. So in floating point numbers, we actually store the negation right here in the corner like- 1. What do I do? Right here on the first bit, if you start as- 1.

So this bit that defines the sign, positive or negative, then we have the exponent and mantissa which is basically the value minus the exponent, right? So for 0 you can actually encode- 0 in triple notation and this is why a negative x if x is 0 becomes a floating point number, right?

And so the problem is if you do 0- x for, if input is 1 then you get- 1, if the input is 0 you get 0, but if you get- x and if you start with 1, then you get with- 1. But if you start with 0, then you get a float.

And you can see how that would ruin the day for the compiler, right? Because compiler makes an assumption that the number it's getting is going to be an integer. And so now, all of a sudden, that assumption is broken. And so the way you ruin your day is if you try to go and access a property using a integer.

This is easy, that's easy. Remember, eex instruction and the xx index, there you go, there's your memory, piece of cake. You put a- 0, it's a float, I can't use floats to access the thing. As a matter of fact, I don't know how it is NUMA registers but the old CPUs had dedicated registers for floating point numbers.

They were separate beasts. If you wanted to deal with floating point numbers, totally different registers, totally different operations you could do on them, et cetera. The arithmetic logic unit for adding and subtracting or multiplying floating points is way different than doing it for regular integers, right? So, the amusing part here is that through a negation, you can end up with a negative floating point number.

If you try to use it in array access or property access, will really mess up the generated code that the compiler has to do. And the result of that is this, that Chrome has been actually pretty good, it's only 3x, it's not too bad. It used to be way worse in the past.

And then Firefox looks like didn't optimize this very well, and so in Firefox is 10x, kind of a difference. Kind of crazy? All these different ways to shoot ourselves in the foot, because JavaScript is dynamic, right? If it'd be statically compiled, then this wouldn't happen because you couldn't do this, right?

You would not be allowed to just randomly do these kinds of things.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now