JavaScript oddities explained. Comparing.


This post is inspired by Brian Leroux and his wtfjs tumblr blog. JavaScript is full of unexplained oddities. I’m going to prove you that every each of them can be explained. There are several causes of such interpreter behavior: inaccurate specification, hyper-correct interpreting, understatements, waywardness of browsers creators. The first type of such ‘features’ are these related to comparing.

0.1 + 0.2 == 0.3

11111111111111113 != 11111111111111111

01234 != 0668

0123 == "0123"

NaN == NaN

3 != "03"

"4" - "1" == "1" + "2"

If you think that TRUE is answer to any of above expressions (and don’t know why it’s exactly opposite) then you should certainly read rest of this article.

ECMAScript

As everybody knows JavaScript is interpreted programming language. Fewer people know that it is best-known dialect of ECMAScript, scripting language standarized by Ecma International in the ECMA-262. JavaScript is aim to be compatible with ECMAScript, while providing additional features not described in the Ecma specification (in third edition). As you may know, not necessarily successfully.

Nowadays, there are generally four popular implementations of ECMAScript:

  1. Gecko used in Mozilla Firefox
  2. V8 used in Google Chrome
  3. Trident used in Internet Explorer
  4. Presto used in Opera

They differ in some aspects but at the same time are mostly compatible with ECMA-262 (well, maybe except Trident). Brendan Eich, the creator of JavaScript, is on record as saying that “ECMAScript was always an unwanted trade name that sounds like a skin disease.”. That’s why we have many JavaScript and no ECMAScript developers.

Comparing

If we read ECMA-262, we will shortly discover very interesting Abstract Equality Comparison Algorithm:

The comparison x == y, where x and y are values, produces true or false. Such a comparison is performed as follows:

  1. If Type(x) is different from Type(y), go to step 14.
  2. If Type(x) is Undefined, return true.
  3. If Type(x) is Null, return true.
  4. If Type(x) is not Number, go to step 11.
  5. If x is NaN, return false.
  6. If y is NaN, return false.
  7. If x is the same number value as y, return true.
  8. If x is +0 and y is -0, return true.
  9. If x is -0 and y is +0, return true.
  10. Return false.
  11. If Type(x) is String, then return true if x and y are exactly the same sequence of characters (same length and same characters in corresponding positions). Otherwise, return false.
  12. If Type(x) is Boolean, return true if x and y are both true or both false. Otherwise, return false.
  13. Return true if x and y refer to the same object or if they refer to objects joined to each other (see 13.1.2). Otherwise, return false.
  14. If x is null and y is undefined, return true.
  15. If x is undefined and y is null, return true.
  16. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y).
  17. If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x)== y.
  18. If Type(x) is Boolean, return the result of the comparison ToNumber(x)== y.
  19. If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
  20. If Type(x) is either String or Number and Type(y) is Object, return the result of the comparisn x == ToPrimitive(y).
  21. If Type(x) is Object and Type(y) is either String or Number, return the result of the comparison ToPrimitive(x)== y.
  22. Return false.

And that’s it. Let me explain first oddities

Null, undefined and NaN

typeof NaN == "number" // true

NaN == NaN // false

type of NaN is ‘number’ (well, NaN means ‘not-a-number’ but says that anyway it’s type is number). On second line mentioned Equality Comparison Algorithm returns false on step 5.

undefined == null // true

Type of null is ‘object’ and type of undefined is ‘undefined’. Because of that we go directly from step 1 to 14. Step 15 ends the algorithm.

Actually undefined and NaN are predefined variables. Surprisingly we can change them:

NaN = "foobar"

undefined = "foobar"

undefined == null // false

undefined == NaN // true

Strings and Numbers

That will be long story.

We can see many times ToNumber function in above algorithm. There’s also long paragraph in ECMA-262 called ToNumber Applied to the String Type. Let me cite part of it:

The conversion of a string to a number value is similar overall to the determination of the number value for a numeric literal (see 7.8.3), but some of the details are different, so the process for converting a string numeric literal to a value of Number type is given here in full.

And actually, result of ToNumber(“01234″) was equal to literal 01234.

Soon after the problem was noticed: Number Literal could not be octal number. So we might write 0xFF and it was equal to 255, but we could not write 01234 (which is 668 in decimal).

They decided to write Annex, so since then 01234 == 668 and 0001234 == 668. However, new notification says that if there is 8 or 9 in Number Literal, leading zeros are stripped and number is treated as decimal.

08 == 010     // true!

01234 == 0668 // true

Moreover they accidentally forgot about ToNumber algorithm, so ToNumber(“01234″) still is 1234. And finnaly that’s why:

0x123 == "0x123" // true

0123 == "0123"   // false!

01234 == "0668"  // true!

Precision

JavaScript is converting all numbers to binary format (even floats) and displaying them in proper form. 0.1 in binary is 0.0 0011 0011 0011 0011 0011 … and so on. Because of that we cannot write 0.1 without loosing precision. And that’s why in JavaScript 0.1 + 0.2 = 0.30000000000000004.

0.1 + 0.2 == 0.3 // false

The same thing happens with

11111111111111113 == 11111111111111111 // true

Except that in that case highest bits are truncated and both 11111111111111111 and 11111111111111113 become 11111111111111112


Add to Del.cio.us

RSS Feed

Add to Technorati Favorites

Stumble It!


Digg It!

        www.sajithmr.com

blog comments powered by Disqus