以下はJavascript : The Curious Case of Null >= 0の日本語訳です。
Why it's important to read the Javascript Spec
JavaScriptの速成コースで作ったソースをコンパイルしていたところ、nullの挙動について面白い現象を発見したので周囲に尋ねてみた。
null > 0; // false
null == 0; // false
null >= 0; // true
ちょっと待って、これは何?
全くもってナンセンスだ。
いったいどのような値が、0より大きくなく、0と等しくもなく、0と等しいかそれより大きい状態になるというのだ?
当初、私はこれはJavaScriptだから仕方ないとスルーしていたが、しかしこの動作はとても奇妙で、ついつい興味をそそられてしまった。
null型とその扱いにどのような関係があるのか、また関係性と等価性のチェックはどのように行われるのだろうか?
そこで、私はこの現象の根本的原因を突き止めるため、JavaScriptの信頼できる唯一の情報源を探すことにした。
つまり、JavaScriptの仕様だ。
そして私は幻覚的な体験を経験したのだった。
The Abstract Relational Comparison Algorithm
まずは比較アルゴリズムを見てみよう。
null > 0; // false
仕様によると、演算子>
と<
は、Abstract Relational Comparison Algorithmというアルゴリズムに従って文がtrue
かfalse
かを返す。
以下はx < y
の比較アルゴリズムである。
1. ToPrimitive(x, Number)を呼んでxをプリミティブ型にする。
2. ToPrimitive(y, Number)を呼んでyをプリミティブ型にする。
3. xの型とyの型が共にStringであれば、16に進む。
4. ToNumber(x)を呼んでxをNumber型にする。
5. ToNumber(y)を呼んでyをNumber型にする。
6. xがNaNであれば`undefined`を返して終了。
7. yがNaNであれば`undefined`を返して終了。
8. xとyの値が同じであればfalseを返して終了。
9. xが+0でyが-0であればfalseを返して終了。
10. xが-0でyが+0であればfalseを返して終了。
11. xが+∞であればfalseを返して終了。
12. yが+∞であればtrueを返して終了。
13. yが-∞であればfalseを返して終了。
14. xが-∞であればtrueを返して終了。
15. xの数値がyの数値より小さければtrueを返して終了。それ以外ならfalseを返して終了。この時点でxy両方とも無限大でも0でもない値であることが保証されている。
16. yの値がxの値のprefixである場合、falseを返して終了。(文字列qが文字列pと文字列rをk連結した結果である場合、pをqのprefixと呼ぶ。rは空文字の可能性もあり、任意の文字列はそれ自身のprefixである。)
17. xの値がyの値のprefixである場合、trueを返して終了。
18. xとyの文字列を順に見ていき、最初に異なる文字が現れる位置kを見付ける。(両者がそれぞれのprefixではないので、そのような数kは必ず存在する。)
19. xのk番目の文字のコードポイントの値をmとする。
20. yのk番目の文字のコードポイントの値をnとする。
21. m < nであればtrueを返す。それ以外ならfalseを返す。
今回の文null > 0
で考えてみよう。
ステップ1と2で、nullと0それぞれにToPrimitiveを呼んでプリミティブ型に変換しようとしている。
ToPrimitiveの結果は以下のようになる。
入力の型 | 出力 |
---|---|
Undefined | 変更なし |
Null | 変更なし |
Boolean | 変更なし |
Number | 変更なし |
String | 変更なし |
Object | デフォルト値を返す。Objectのデフォルト値はobject型の内部メソッドDefaultValueによって決められ、第2引数は無視される。 |
変換テーブルに従うと、nullも0もどちらも変換されない。
よってステップ3には当てはまらず、ステップ4と5で両辺がNumberへと変換される。
Numberへの変換は以下の表のようになっている。
入力の型 | 出力 |
---|---|
Undefined | NaN |
Null | +0 |
Number | 変更なし |
Boolean | trueなら1、falseなら+0 |
... | ... |
StringとObjectは今回無関係であるため省略した。
詳細はここから確認できる。
nullは+0となり、0は0のままになる。
どちらの値もNaNではないのでステップ6と7はスキップする。
ステップ8で+0と0は等しいと評価され、アルゴリズムはfalseを返すこととなる。
以上より、
null > 0; // false
null < 0; // false
両方ともfalseになる。
The Abstract Equality Comparison Algorithm
次に進もう。
null == 0; //false
これは興味深い結果だ。
==
演算子はAbstract Equality Comparison Algorithmというアルゴリズムに従い、true
かfalse
を返す。
1. xとyの型が異なる場合、14に進む。
2. xの型がundefinedであればtrueを返して終了。
3. xの型がNullであればtrueを返して終了。
4. xの型がNumberでない場合、11に進む。
5. xがNaNであればfalseを返して終了。
6. yがNaNであればfalseを返して終了。
7. xとyが同じ数値であればtrueを返して終了。
8. xが+0でyが-0であればtrueを返して終了。
9. xが-0でyが+0であればtrueを返して終了。
10. falseを返して終了。
11. xの型がStringである場合、xとyの文字列が全く同じであればtrueを返して終了。それ以外ならfalseを返して終了。
12. xの型がBooleanである場合、xとyの両方がtrueか、両方がfalseであればtrueを返して終了。それ以外ならfalseを返して終了。
13. xとyが同じObjectを参照しているか、結合オブジェクトである場合、trueを返して終了。それ以外ならfalseを返して終了。
14. xがnullでyがundefinedであればtrueを返して終了。
15. xがundefinedでyがnullであればtrueを返して終了。
16. xの型がNumberでyの型がStringの場合、x==ToNumber(y)の結果を返して終了。
17. xの型がStringでyの型がNumberの場合、ToNumber(x)==yの結果を返して終了。
18. xの型がBooleanの場合、ToNumber(x)==yの結果を返して終了。
19. yの型がBooleanの場合、x==ToNumber(y)の結果を返して終了。
20. xの型がStringかNumberでyの型がObjectの場合、x==ToPrimitive(y)の結果を返して終了。
21. xの型がObjectでyの型がStringかNumberの場合、ToPrimitive(x)==yの結果を返して終了。
22. falseを返して終了。
nullと0の比較では、まず型が異なるためステップ1からステップ14に飛ぶ。
nullの型はNullなので、ステップ14から21までは全て当てはまらない。
最終的にステップ22まで進み、結果はデフォルト値のfalseとなる。
従って、
null == 0; //false
となる。
The Greater-than-or-equal Operator (>= )
最後に問題のこれをチェックしていこう。
null >= 0; // true
これは仕様による理解を完全に放棄したところだ。
高度に政治的な判断によると、関係演算子>=
は以下のように評価される。
null < 0 がfalseであれば、null >= 0 がtrueになる
Really?
従って、
null >= 0; // true
実のところ、この結果は理に叶っている。
数学的には、2数xyがあって、xがyより小さくないのであれば、xはyより大きいかyと同じでないといけないからだ。
この式の評価は、最適化のため以下のように行われたのではないかと考えている。
まずx>y
を評価する。
これがfalseだった場合、次にx==y
を評価する。
それでもなければ最後にx<y
を評価しなければならないところだが、x>y
の結果から即trueを返しているのではないか。
( >=
演算子の正確な評価は、ここで見付けることができる )
・・・
このように些細な疑問でも、答えを調べていく過程でいくつもの新しい発見がありました。
この記事が貴方の助けになることを願っています。
・・・
もっと助けが必要?
興味があるならTwitterやGithubで活動しています。
まとめ
>=
の評価の項目には、
【x<y
がtrueかundefinedならfalseを返し、それ以外ならtrueを返す】
つまりx<y
とx>=y
は反対の値になる、と書かれているように見えるのだが違うのだろうか。
null > 0; // false
null == 0; // false
null < 0; // false
null >= 0; // true
null <= 0; // true
Oh.