Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Nullの奇妙な比較、あるいはJavaScriptの仕様を読むことが重要な理由

More than 1 year has passed since last update.

以下はJavascript : The Curious Case of Null >= 0の日本語訳です。

Why it's important to read the Javascript Spec

1-F-EDLK-OugJ_4KOgtJqnPA.png

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というアルゴリズムに従って文がtruefalseかを返す。
以下は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というアルゴリズムに従い、truefalseを返す。

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を返しているのではないか。

( >=演算子の正確な評価は、ここで見付けることができる )

・・・

このように些細な疑問でも、答えを調べていく過程でいくつもの新しい発見がありました。
この記事が貴方の助けになることを願っています。

・・・

もっと助けが必要?
興味があるならTwitterGithubで活動しています。

まとめ

>=の評価の項目には、
x<yがtrueかundefinedならfalseを返し、それ以外ならtrueを返す】
つまりx<yx>=yは反対の値になる、と書かれているように見えるのだが違うのだろうか。

    null > 0;  // false
    null == 0; // false
    null < 0;  // false

    null >= 0; // true
    null <= 0; // true

Oh.

rana_kualu
不労所得で生きたい。
https://twitter.com/rana_kualu
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away