Equality and Relational Operators: Comparing the strange relationship between null and 0

Recently I saw a tweet about the relationship between values in JavaScript saying that greater or equals means nothing. The tweet reported the following results:

null >= 0 // true
null > 0 // false
null == 0 // false

My experience with the JavaScript language makes me believe that everything has a meaning, even if it looks weird or like a malfunction on a higher level. So, I took some time to investigate the ECMAScript specs to understand how to explain these results.

null >= 0 is true

I started with the first case. Why does null >= 0 come out as true? I couldn’t say. So, I searched in the specs where >= is defined and found the relational operators:

  RelationalExpression[?In, ?Yield] >= ShiftExpression[?Yield]

Now, I needed to find how the RelationalExpression is evaluated:

RelationalExpression : RelationalExpression >= ShiftExpression

  1. Let lref be the result of evaluating RelationalExpression.
  2. Let lval be ? GetValue(lref).
  3. Let rref be the result of evaluating ShiftExpression.
  4. Let rval be ? GetValue(rref).
  5. Let r be the result of performing Abstract Relational Comparison lval < rval.
  6. ReturnIfAbrupt(r).
  7. If r is true or undefined, return false. Otherwise, return true.

Running through the evaluation step by step, I could say that lref is null, and lval is the result of GetValue(lref). This means that lval will be null:

6.2.3.1 GetValue (V)

...
2. If Type(V) is not Reference, return V.
...

The same happens with the 0 operand, where rref and rval will be 0.

The important part, you might notice, is at step 5: performing Abstract Relational Comparison lval < rval. Let’s check out what it does:

1. If the LeftFirst flag is true, then
  a. Let px be ? ToPrimitive(x, hint Number).
  b. Let py be ? ToPrimitive(y, hint Number).

The comparison here is not provided by a LeftFirst flag, and it’s default value is true, so px is the result of ToPrimitive(x, ...) and py is the result of ToPrimitive(y, ...). As both null and 0 are primitive values, ToPrimitive returns them without any conversion. Now, we can proceed to the following steps:

3. If both px and py are Strings, then

We know both px and py are not Strings though, right?

4. Else
  a. Let nx be ? ToNumber(px). Because px and py are primitive values evaluation order is not important.
  b. Let ny be ? ToNumber(py).

The above reflects the most important point that defines the final result for the >= relationship operation. The values will be converted to their number representation. You can check the ToNumber method to understand that null is converted to +0 and 0 as a Number value has no conversion.

Now, we know nx is +0 (or simply 0) and ny is 0 too, and they meet in the following step 4.e:

4. Else
  ...
  e. If nx and ny are the same Number value, return false.

Remember that the Abstract Relational Comparison was called to compare if x < y, and for sure, that is false. If we go back to our RelationalExpression evaluation, we find the final result at step 7:

RelationalExpression : RelationalExpression >= ShiftExpression

  ...
  5. Let r be the result of performing Abstract Relational Comparison lval < rval.
  ...
  7. If r is true or undefined, return false. Otherwise, return true.

As r is false, the evaluation returns the opposite value, true.

Returning back to null >= 0, we can finally say the relational comparison of null and 0 is made from their numeric representation. The number that represents null is 0, so it makes it more clear to say the operation is the equivalent for 0 >= 0. I’m sure you will agree with me that’s true.

Let’s see what happens on the next operation.

null > 0 is false

The > on null > 0 is another relational operator, and it is evaluated as the following:

RelationalExpression : RelationalExpression > ShiftExpression

  1. Let lref be the result of evaluating RelationalExpression.
  2. Let lval be ? GetValue(lref).
  3. Let rref be the result of evaluating ShiftExpression.
  4. Let rval be ? GetValue(rref).
  5. Let r be the result of performing Abstract Relational Comparison rval < lval with LeftFirst equal to false.
  6. ReturnIfAbrupt(r).
  7. If r is undefined, return false. Otherwise, return r.

This case is very similar to the previous one we explored, with the difference being that Abstract Relational Comparison is now called with LeftFirst being false. This means only the value on the right is parsed first on the ToPrimitive operation:

7.2.12 Abstract Relational Comparison

1. If the LeftFirst flag is true, then
  ...
2. Else the order of evaluation needs to be reversed to preserve left to right evaluation
  a. Let py be ? ToPrimitive(y, hint Number).
  b. Let px be ? ToPrimitive(x, hint Number).

As we’ve seen before, both null and 0 are already their primitive representations, so they remain with the same values for py and px and will pass through the same ToNumber operation on steps 4.a and 4.b.

What’s the result after evaluating 0 < 0?

  1. false
  2. An emoji representing me (Leo) wearing my round glasses and looking to your left

Unfortunately, in JavaScript, this is not an emoji operator, and it simply returns false as you can see here:

RelationalExpression : RelationalExpression > ShiftExpression

  ...
  5. Let r be the result of performing Abstract Relational Comparison rval < lval with LeftFirst equal to false.
  ...
  7. If r is undefined, return false. Otherwise, return r.

Now that we know both >= and < compare the left and right values using their numeric representations, what happens with the == operator?

null == 0 is false

As I mentioned before, >= and < are both relational expressions and based on the relationship named by the operator between the numeric representation of both operands. This is different for the == operator, which is not a relational expression anymore, it’s actually a equality operator

EqualityExpression[In, Yield]:
  RelationalExpression[?In, ?Yield]
  EqualityExpression[?In, ?Yield] == RelationalExpression[?In, ?Yield]
  ...

Notice that the operands can be a relational expression. I’m not going to explain it in this post, but this means we can compare their results, such as null >= 0 == null < 0 evaluated as true == false returning false.

Let’s check how this evaluation happens:

EqualityExpression : EqualityExpression == RelationalExpression

  1. Let lref be the result of evaluating EqualityExpression.
  2. Let lval be ? GetValue(lref).
  3. Let rref be the result of evaluating RelationalExpression.
  4. Let rval be ? GetValue(rref).
  5. Return the result of performing Abstract Equality Comparison rval == lval.

The first four steps are similar to what we’ve seen before in the evaluation for relational expressions. We know on null == 0 the values for lval is null and rval is 0.

Now, we have to check the result of Abstract Equality Comparison rval == lval.

7.2.13 Abstract Equality Comparison

  1. If Type(x) is the same as Type(y), then
    a. Return the result of performing Strict Equality Comparison x === y.
  2. If x is null and y is undefined, return true.
  3. If x is undefined and y is null, return true.
  4. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y).
  5. If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.
  6. If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.
  7. If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
  8. If Type(x) is either String, Number, or Symbol and Type(y) is Object, return the result of the comparison x == ToPrimitive(y).
  9. If Type(x) is Object and Type(y) is either String, Number, or Symbol, return the result of the comparison ToPrimitive(x) == y.
  10. Return false.

We can see there’s no ToNumber conversion on the operands, we are now checking their actual value representation, not even a ToPrimitive conversion is happening here.

If we run through the given steps, we end up on step 10, which returns false, and that’s the final result for our null == 0 operation.

Isn’t null an object?

You might consider null an object because typeof null returns "object". That’s misleading because the real type of null is null and not an Object. You can check it at the value types we have specified.

That’s why null values don’t return on the steps 8 or 9 on the abstract equality comparison:

7.2.13 Abstract Equality Comparison

  ...
  8. If Type(x) is either String, Number, or Symbol and Type(y) is Object, return the result of the comparison x == ToPrimitive(y).
  9. If Type(x) is Object and Type(y) is either String, Number, or Symbol, return the result of the comparison ToPrimitive(x) == y.
  ...

The Type for null is not Object, and null == 0 is finally false for any matters.

Conclusion

As humans, we tend to see >=, <, and == leading to the same mathematical comparisons, but JavaScript is a programming language and not a scientific calculator even if it’s a great tool for the Internet of Things. These differences are fine on JavaScript as even constant values such as PI are represented only by an approximate numerical value as you can see in the specs for Math.PI.

While the null >= 0 and null < 0 are comparing their relationship using their numeric representations, == is not a mathematical comparison, but a check for the operands’ equivalency represented by their native values in the language. Their relationship is evaluated as simply programming language values.

Rather than categorizing something as a wat moment on a programming language, it’s more useful to explore and understand how and why it works out that way. Doing so might even help to figure out the most appropriate question for the presented cases: why is your code trying to compare null with 0?

I hope you can now see more clearly what is happening for these distinct operators so that their results are less confusing. Do you have any suggestions or feedback? Let’s discuss it! My appreciation is == 1.

Comments

Contact Us

We'd love to hear from you. Get in touch!

Boston

201 South Street, Boston, MA 02111

New York

315 Church St, New York, NY 10013

Phone & Email

(617)379-2752 hello@bocoup.com