hnwの日記

「===」がtrueを返し「==」がtrueを返さないサンプル


(2015/04/01追記)本稿の内容はPHP 5.3.xまでに当てはまります。PHP 5.4.0以降はINF==INFになりました。「PHP 5.4.0から無限大==無限大がtrueになってた - hnwの日記」もご確認ください。


Yahooの入社試験だという、PHPについての22問の質問が話題ですね。どれも面白い質問で、特に口頭試問で使って相手の反応が見たい問題だと感じましたが、その8問目を見て驚きました。

8. 「===」は何をするか? 「==」がtrueを返し「===」がtrueを返さないサンプルを示せ。

Do You PHP はてな - Yahoo!がPHPエンジニアを雇う時に聞く質問

単なる勘違いなんですが、僕は10秒くらい逆の意味に取っていました。「恐ろしくマニアックな質問をするんだなー、そんなことを知っているからって仕事の役に立つのか?」などと考えてしまいましたが、そんなわけはないですね。


それはさておき、聞かれていない質問「$a===$bかつ$a!=$bとなるような組み合わせを挙げよ」に対する解答は僕が知る限り2組しかありません。他に僕が知らない組み合わせがあるかもしれませんし、未来のバージョンで増えたり減ったりするかもしれませんけどね。


パッと解答できる人は滅多に居ないと思います。お暇な方はここで目を閉じて考えてみてください。考える価値はあまり無いと思いますけど。


さて、早速このムダ知識を披露してみます。テストしたPHPは5.1.6ですが、僕の知る限りこのあたりの挙動は変わっていないはずです。

$ php -r '$a=1e500; var_dump($a, $a === $a, $a != $a);'
float(INF)
bool(true)
bool(true)
$ php -r '$a=-1e500; var_dump($a, $a === $a, $a != $a);'
float(-INF)
bool(true)
bool(true)
$

というわけで、(∞,∞)と(-∞,-∞)の2組が僕の示す解答です。念のため補足しておくと、IEEE64ビット浮動小数点数で扱える0以上の数の範囲は約4.94 \times 10^{-324}〜約1.80 \times 10^{308}ですが、この範囲で扱えないほど大きい値は無限大(inf)となります。


で、こうしてムダ知識を披露したところで終わってもいいんですけど、何故こんなことが起こるのかについて、毎度毎度ですがPHPソースコードをチェックしてみましょう。


「===」の処理の実体はZend/zend_operators.cのis_identical_function()です。型が浮動小数点数同士の場合に関して言うと、次のように単純にCの==演算子で比較しています。NaN以外の浮動小数点数は、自分同士の==が成り立ちます。よってPHPの世界では∞===∞です。

                case IS_DOUBLE:
                        result->value.lval = (op1->value.dval == op2->value.dval);
                        break;

一方、「==」の処理はZend/zend_operators.cのcompare_function()に記述してあります。これも浮動小数点数同士の場合に関して言うと、下記のコードが実行されます。

        if ((op1->type == IS_DOUBLE || op1->type == IS_LONG)
                && (op2->type == IS_DOUBLE || op2->type == IS_LONG)) {
                result->value.dval = (op1->type == IS_LONG ? (double) op1->value.lval : op1->value.dval)
                                     - (op2->type == IS_LONG ? (double) op2->value.lval : op2->value.dval);
                result->value.lval = ZEND_NORMALIZE_BOOL(result->value.dval);
                result->type = IS_LONG;
                COMPARE_RETURN_AND_FREE(SUCCESS);
        }

要するに、2つの数の差が0だったら等しいと判断しています。しかし、∞-∞の結果はNaNであり、0ではありません。よってPHPの世界では∞!=∞です。


この「==」の実装が適当なのかどうかの議論ですが、適当不適当と言うより非常に不思議だと僕は感じます。というのも、異なる浮動小数点数の差が0になることはありえないはずですから、Inf、-Inf、NaN以外の数について言えば、差が0かどうかを調べるのと==で比較するのとは何も変わりません。言い換えると、これは意図的に∞!=∞、-∞!=-∞の2つを実現しているようにも見えます。でも、何のためにそんなことをする必要があるんでしょう?なかなか不思議です。