JavaScriptでundefinedの大小比較はつねにfalse。その理由を、仕様を引用して解説する


JavaScriptでは、undefinedの大小比較の結果は常にfalseです。

undefined > 0 // false
undefined >= 0 // false
undefined < 0 // false
undefined <= 0 // false

undefined同士の大小比較でも常にfalseです。

undefined > undefined // false
undefined >= undefined // false
undefined < undefined // false
undefined <= undefined // false

なぜなのか、仕様を確認してみましょう。

大小比較 共通手順

大小比較のアルゴリズムは、Abstract Relational Comparisonの章に記載があります。

x < ytruefalseまたはundefinedを返します。undefinedが返る時は、少なくとも片方がNaN(非数)であることを表します。

x < yの判定のうち、片方(または両方)がundefinedのときのステップを中心に、以下に記載します。

  1. xをプリミティブ型(Boolean, Null, Undefined, Number, BigInt, String, Symbolのいずれか)に変換する
  2. yをプリミティブ型(Boolean, Null, Undefined, Number, BigInt, String, Symbolのいずれか)に変換する
  3. 変換した結果、両方ともStringであれば、
    1. xyを辞書順で並べ、xの方が先に来るならばtrue、そうでないならばfalseを返す(詳しくは仕様を参照してください)
  4. それ以外であれば、
    1. xをさらにNumberに変換する
    2. yをさらにNumberに変換する
    3. xが`NaNであればundefinedを返す
    4. yが`NaNであればundefinedを返す
    5. xyを数値として比較し、xの方が小さければtrue、そうでないならばfalseを返す(詳しくは仕様を参照してください)

undefinedの大小比較 共通手順を追う

例として、undefined < 0のときの処理を追ってみましょう。
上の手順に当てはめると、x = undefinedy = 0です。

  1. xをプリミティブ型(Boolean, Null, Undefined, Number, BigInt, String, Symbolのいずれか)に変換する
    undefinedをプリミティブ型に変換するとundefined(変化なし)です。
  2. yをプリミティブ型(Boolean, Null, Undefined, Number, BigInt, String, Symbolのいずれか)に変換する
    0をプリミティブ型に変換すると0(変化なし)です。
  3. 変換した結果、両方ともStringであれば、
    → 条件に当てはまらないので次に進みます
  4. それ以外であれば、
    1. xをさらにNumberに変換する
    2. yをさらにNumberに変換する
      Numberへの変換表はこちらです。undefinedNaNに変換され、00のままです。
データ型 返却値
Undefined NaN
Null +0
Boolean trueならば1、falseならば+0
Number 元の値
String 文字列が数字ならばその数字、それ以外はNaN。詳細はToNumber Applied to the String Typeを参照
Symbol TypeError exceptionを投げる

nulltruefalseが数値に変換されるのも興味深いです。これらに言及した記事を参考資料としてリンクしました。

次に、 3. xNaNであればundefinedを返す
xの値を変換した結果がNaNなので、undefinedが返されます。

さて、ここまででundefined < 0の評価値はundefinedであることがわかりました。
しかし、実際はundefined < 0の評価結果はfalseです。何が間違っているのでしょうか。
それを解き明かすには、もうひとつの仕様を読む必要があります。

大小比較の実行手順

大小比較の実行手順はRuntime Semantics: Evaluationに記載があります。

主要な部分だけ抜粋します。

x < yの評価方法は、
1. 大小比較 共通手順でx < yを評価する
2. 結果がundefinedであればfalse、それ以外であれば評価結果を返す

x > yの評価方法は、
1. 大小比較 共通手順でy < xを評価する
2. 結果がundefinedであればfalse、それ以外であれば評価結果を返す

x <= yの評価方法は、
1. 大小比較 共通手順でy < xを評価する
2. 結果がtrueまたはundefinedであればfalse、それ以外であればtrueを返す

x >= yの評価方法は、
1. 大小比較 共通手順でx < yを評価する
2. 結果がtrueまたはundefinedであればfalse、それ以外であればtrueを返す

undefinedの大小比較の実行手順を追う

undefined < 0のときの処理を追ってみましょう。
x < yの評価方法を見ます。
1. 大小比較 共通手順でx < yを評価する
→ 上で確認したように、undefined < 0の評価結果はundefinedです。
2. 結果がundefinedであればfalse、それ以外であれば評価結果を返す
undefinedなので、falseが返ります。

x > yx <= yx >= yのそれぞれについても、undefined < 0の結果がundefinedなので、全ての比較においてfalseが返ることがわかりました。

x < yfalseだからといって、x >= yが必ずしもtrueにならないことがあるのですね。

参考資料

あとがき

意外にも、undefinedの大小比較についての記載が見当たらず苦労しました。
数値同士や文字列同士以外を比較するようなコードを、実際に仕事で書くことがあるとしたら多分アプリの設計ミスなのですが、undefinedが紛れ込むことはあるかなーと思い、気をつけようと思いました。