かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

javascript 文字列のまま数字を比較すると危険が危ない。

例えば入力された数字をそのまま比較するなどすると、数字が文字列になっていると予期せぬ挙動をする事があります。

// 引数を比較して大きい方を返す関数
var f = function(a, b) {
  if(a == b) {
    return "eq"
  } else {
    return (a > b)? a : b;
  }
}

// 数字を比較
f(5, 55); // 55
f(6, 55); // 55

// 文字列の数字を比較
f("5", "55"); // "55"
f("6", "55"); // "6" ヾ(ヽ*ω*) ヌ!?
f("50", "55"); // "55"
f("56", "55"); // "56"
f("56", "550"); // "56" !!!

// 片方が数値の場合
f(5, "55"); // "55"
f(6, "55"); // "55"
f(50, "55"); // "55"
f(56, "55"); // 56
f(56, "550"); // "550"

挙動の秘密は文字列の比較にありそうです。

// 単純な文字列を比較
f("a", "b"); // "b"
f("c", "b"); // "c"
f("a", "aa"); // "aa"
f("b", "aa"); // "b"
f("aa", "ac"); // "ac"
f("ab", "ac"); // "ac"
f("ac", "ac"); // "eq"
f("ad", "ac"); // "ad"
f("ad", "aca"); // "ad"

Stringオブジェクトを見てみる

new String("6")
=> String {0: "6", length: 1, [[PrimitiveValue]]: "6"}
new String("55");
=> String {0: "5", 1: "5", length: 2, [[PrimitiveValue]]: "55"}

文字列での比較の場合は、文字列の長さ(length)は考慮されず、先頭の文字から順番に比較していき、同じ値なら次の文字、不等号が成立した時点でその結果を返しているようです。
"55"のような文字列化された数字どうしだと自動的に数値化されるのではなく、文字列として比較されるようです。
比較する片方が数値の場合は自動的に数値に変換して比較が行われるようです。文字列化された数字の先頭に0xがついてると16進数として判断されます。
先頭に0や半角スペースがあってもこれは無視されるようです。

f(5, "055"); // "055"
f(6, "055"); // "055"
f(56, "055"); // 56

f(5, " 55"); // " 55"
f(6, " 55"); // " 55"
f(56, " 55"); // 56

文字列化された方の数字にpxのような単位が付いていると文字列の方が大きいと判定されるようです。

f(5, "5px"); // "5px"
f(6, "5px"); // "5px"
f(60, "5px"); // "5px"
f(600, "5px"); // "5px"
f(6000, "5px"); // "5px"

数値として比較したい場合はキチンと数値化しましょう。というお話でした。


[参考]

JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス

JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス