16 Oct 2016 - by 'Maurits van der Schee'
A few days ago, a colleague pointed out some unexpected behavior in JavaScript. After a few hours of investigation this weekend I think I have found the cause: a bug in boolean comparison. This post shows my investigation and will help you to reach that same conclusion.
What does the following expressions evaluate to?
> [] && [] == false
true
Yes, it is "true"! If you don't believe me, try it. The question that is addressed in the remainder of this post is: "Really? Why?!"
You may think that empty arrays evaluate to "false" and "false && false" evaluates to "false" leaving "false == false" which then evaluates to "true". That would have been an easy explanation, but it is wrong. In JavaScript empty arrays evaluate to "true" (when converted to a boolean) as you can see here:
> ([]?true:false)
true
> Boolean([])
true
Let's test a few script languages to see what they evaluate empty array to, when used as a boolean expression:
$ nodejs -e 'console.log([] ? "true" : "false")'
true
$ php -r 'print ([] ? "true" : "false");'
false
$ perl -e 'print ([] ? "true" : "false")'
true
$ perl -e 'print (() ? "true" : "false")'
false
$ ruby -e 'print ([] ? "true" : "false")'
true
$ ruby -e 'print (() ? "true" : "false")'
false
$ python -c 'print ("true" if [] else "false")'
false
As you can see the empty array in Perl and Ruby also evaluates to "true", while empty lists evaluate to "false". So all languages choose the (in my opinion) more logical "false" value to evaluate to (in one way or another). When we replace the empty array with "true" the expression behaves as expected:
> true && true == false
false
It almost seems as if in this case the empty array is not evaluated to "true". That can't be the case or can it?
I tried the following to find out whether or not this had to do with operator precedence:
> ([] && []) == false
true
> [] && ([] == false)
true
But since both evaluate to true, I think we can rule operator precedence out. We have to dig a little deeper.
I got the feeling it had something to do with the way script languages evaluate logical expressions.
$ nodejs -e 'console.log((2 && 3) > 2)'
true
$ php -r 'var_export((2 && 3) > 2);'
false
$ perl -e 'print ((2 && 3) > 2)'
1
$ ruby -e 'print ((2 && 3) > 2)'
true
$ python -c 'print ((2 and 3) > 2)'
True
Instead of returning true or false most scripting languages (PHP excluded) return the first "falsy" (evaluates to false) or the second "truthy" (evaluates to true) argument of the "&&" or "and" expression. You can see that here in more detail:
> 0 && 1
0
> 2 && 3
3
Now let's see whether or not logical operator evaluation matters by replacing the "[]" (empty array) by a "1" (one), another non-boolean that also evaluates to "true":
> Boolean(1)
true
> 1 && 1 == false
false
So somehow if we replace "[]" (empty array) with "1" (one) we get correct results. Since "1" (one) does not show the (strange) behavior that the empty array shows, the behavior seems to be unrelated to the way logical operators are evaluated.
So, let's approach it from the other side now. The only way we can construct the result "true" is when the "==" (comparison) operator fails to convert correctly to the type of the left or right hand side of the comparison expression. Let's try all possible paths:
> Boolean([])==Boolean(false)
false
> Number([])==Number(false)
true
> String([])==String(false)
false
> Object([])==Object(false)
false
You would expect the first to happen (or the last), because that seems the logical way to convert both sides to the same type. Apparently this is not true and the comparison expression between an object and a boolean is evaluated as a numerical comparison (a type that is unrelated to both the left and the right side). Don't you believe me? Read on.
Let's see what the EcmaScript 6 standard states on "Equality Comparison":
The comparison x == y [...] is performed as follows: [...] If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
It states (as the first matching step of the comparison algorithm) that comparison with a boolean on the right hand side should be done by converting that boolean to a number, just like we expected from our analysis.
An empty array in JavaScript is evaluated to true when converted to type Boolean. But when comparing a boolean with an object neither the type of the left hand nor the type of the right side is chosen to evaluate the comparison in (instead Number is chosen). This may be wrong, but we have seen that it is in line with the specification in the EcmaScript standard.
> Boolean([])
true
> [] == false
true
Now that I studied the standard I was able to come up with a similar case. Can you now reason why this one evaluates to "true"?
> "0" == !"0"
true
The above JavaScript statements clearly show the problem and this 5 year old StackOverflow post confirms that the comparison was indeed done numerical. I don't expect a fix for this problem any time soon as this is a problem in the standard and not in the implementation.
PS: Liked this article? Please share it on Facebook, Twitter or LinkedIn.