2010/07/21

JavaScriptは比較演算子"も"推移関係が成り立っていない件

そういえばすっかり三連休も終わってたことだし,すっかり存在を忘れていたブログに小ネタでもメモっておくかー

tweet もしたけど (http://twitter.com/Maki_Daisuke/status/18780506275),タイトルの通りのおはなしです.
推移関係 (transitive relation) っていうのは,「風が吹けばほこりがたつ」「ほこりがたてば(略)桶屋が儲かる」だから「風が吹けば桶屋が儲かる」っていう三段論法みたいなやつですね.
一般的には関係R が,どんな組み合わせの a, b, c についても下のように言えるとき,R は推移的であるといいます:
a R b で b R c なら a R c
数値の大小比較が推移的だというのは直感的にわかるはず.
a < b で b < c なら a < c
ですもんね.
で,上で使った「<」演算子が,JavaScriptでは推移的ではないよ,というのが本題.
推移的でないことを示すのは簡単で,ひとつでもいいから上の法則が成り立っていない a, b, c の組み合わせを見つけてやればいいだけ.
ここで以下の組み合わせを考える:
a = 1;
b = "2";
c = "three";
さて,実行してみると結果は以下のとおり:
a < b  =>  1   < "2"      =>  true
b < c  =>  "2" < "three"  =>  true
a < c  =>  1   < "three"  =>  false
見事に反例を示すことが出来ました.

なんでこんな結果になるのかというと,JavaScriptの「<」は,両辺が文字列だった場合は文字列の辞書順で大小比較をするんですね.
どちらか片方でも文字列でなかった場合は,数値に変換して比較するわけです.
そのため,値の型が揃っていない場合にはこんな珍妙なことが起きるというわけですね.

そういえばタイトルに「"も"」と書きましたが,JavaScriptでは同様に「==」演算子も推移関係を満たしていないのですよ.
これはECMAScriptの仕様書で言及されているくらい有名な(?)事実:
NOTE 3 The equality operator is not always transitive. For example, there might be two distinct String objects, each representing the same String value; each String object would be considered equal to the String value by the == operator, but the two String objects would not be equal to each other.
文章で読むとなんだかわかりにくいですが,こういう意味ですね↓
a = new String("hoge");
b = "hoge";
c = new String("hoge");
として,皆さんも実行してみてください:
a == b  =>  true
b == c  =>  true
a == c  =>  false
ほらね!

だったら「<」についてもNOTEしておけよ!とか思ったのは,別の話(ぉ