Hatena::ブログ(Diary)

hnwの日記 このページをアンテナに追加 RSSフィード

[プロフィール]
 | 

2008年6月6日(金) PHPの==がキモい件 このエントリーを含むブックマーク このエントリーのブックマークコメント

どうやら僕はround()の人なんだそうです(参考資料)。それはそうとして、もう少し前までは==の人だった気がしますので、その頃の話題を再掲します。


僕は2年ほど前に「==がキモい件」などのタイトルで勉強会で発表していた頃がありました。PHPの==演算子の挙動について、啓蒙が必要だろうと考えていたためです。当時に比べれば最近はPHPの==演算子の不思議な挙動に関する記事を見る気がしますが、まだまだキモさの全容を把握している人は少ないような気もします。


PHPの==演算子の何がキモいのか、一言で言うと両辺の値の型をテキトーに合わせて比較する点です。言い換えると、「型はさておき、等しい値を意味しているようならtrue」という演算子です。


マニュアルの「PHP 型の比較表」の「==による緩やかな比較」を見てもキモさがチラホラ見えるんですが、今回は更に突っ込んだ比較表を作ってみました。


まずは以前の勉強会で紹介した表のダイジェスト版を紹介します。主にfalseっぽい値の比較表です。推移律(a==bかつb==cならa==c)が成り立たない場所が何カ所か存在します。

array(0) { } NULL bool(false) int(0) string(0) "" string(1) "0" string(2) "00" string(3) "0.0" string(1) " " string(1) "" bool(true)
array(0) { } truetruetruefalsefalsefalsefalsefalsefalsefalsefalse
NULL truetruetruetruetruefalsefalsefalsefalsefalsefalse
bool(false) truetruetruetruetruetruefalsefalsefalsefalsefalse
int(0) falsetruetruetruetruetruetruetruetruetruefalse
string(0) "" falsetruetruetruetruefalsefalsefalsefalsefalsefalse
string(1) "0" falsefalsetruetruefalsetruetruetruefalsefalsefalse
string(2) "00" falsefalsefalsetruefalsetruetruetruefalsefalsetrue
string(3) "0.0" falsefalsefalsetruefalsetruetruetruefalsefalsetrue
string(1) " " (半角スペース1文字) falsefalsefalsetruefalsefalsefalsefalsetruefalsetrue
string(1) "" (ヌル文字1文字、0x00)falsefalsefalsetruefalsefalsefalsefalsefalsetruetrue
bool(true) falsefalsefalsefalsefalsefalsetruetruetruetruetrue

次に浮動小数点数を比較してみました。INFやNANが怪しい気もしますが、他は普通です。

float(0) float(1.0E+300) float(1.0E-300) float(INF) float(-INF) float(NAN)
float(0) truefalsefalsefalsefalsefalse
float(1.0E+300) falsetruefalsefalsefalsefalse
float(1.0E-300) falsefalsetruefalsefalsefalse
float(INF) falsefalsefalsefalsefalsefalse
float(-INF) falsefalsefalsefalsefalsefalse
float(NAN) falsefalsefalsefalsefalsefalse

次は0っぽい値の比較表です。表を作る意味がないですね。

int(0) float(0) string(1) "0" string(2) "00" string(3) "0.0" string(3) "0x0" string(3) "0X0" string(3) "0e1" string(3) "0E1" string(7) "1e-1000"
int(0) truetruetruetruetruetruetruetruetruetrue
float(0) truetruetruetruetruetruetruetruetruetrue
string(1) "0" truetruetruetruetruetruetruetruetruetrue
string(2) "00" truetruetruetruetruetruetruetruetruetrue
string(3) "0.0" truetruetruetruetruetruetruetruetruetrue
string(3) "0x0" truetruetruetruetruetruetruetruetruetrue
string(3) "0X0" truetruetruetruetruetruetruetruetruetrue
string(3) "0e1" truetruetruetruetruetruetruetruetruetrue
string(3) "0E1" truetruetruetruetruetruetruetruetruetrue
string(7) "1e-1000" truetruetruetruetruetruetruetruetruetrue

次は1っぽい値の比較表です。これまた無意味な表です。

int(1) float(1) string(1) "1" string(2) "01" string(3) "1.0" string(3) "0x1" string(3) "0X1" string(3) "1e0" string(3) "1E0" string(5) "10e-1" string(6) "0.1e+1"
int(1) truetruetruetruetruetruetruetruetruetruetrue
float(1) truetruetruetruetruetruetruetruetruetruetrue
string(1) "1" truetruetruetruetruetruetruetruetruetruetrue
string(2) "01" truetruetruetruetruetruetruetruetruetruetrue
string(3) "1.0" truetruetruetruetruetruetruetruetruetruetrue
string(3) "0x1" truetruetruetruetruetruetruetruetruetruetrue
string(3) "0X1" truetruetruetruetruetruetruetruetruetruetrue
string(3) "1e0" truetruetruetruetruetruetruetruetruetruetrue
string(3) "1E0" truetruetruetruetruetruetruetruetruetruetrue
string(5) "10e-1" truetruetruetruetruetruetruetruetruetruetrue
string(6) "0.1e+1" truetruetruetruetruetruetruetruetruetruetrue

10っぽい値の比較表も作ってみました。これは少し毛色が違います。

int(10) string(3) "1e1" string(4) "10e0" string(4) "10a0" string(4) "10a1" string(4) "10e1"
int(10) truetruetruetruetruefalse
string(3) "1e1" truetruetruefalsefalsefalse
string(4) "10e0" truetruetruefalsefalsefalse
string(4) "10a0" truefalsefalsetruefalsefalse
string(4) "10a1" truefalsefalsefalsetruefalse
string(4) "10e1" falsefalsefalsefalsefalsetrue

長々と解説をするより比較表を眺めてもらった方がインパクトがありそうですので、細かい説明は省略します。興味のある方は参考リンクを見てください。また、自分なりの比較表を作りたい人のために、この表を出力する投げやりなPHPスクリプトも公開します。楽しい表が出来たら僕にも見せてください。(ダウンロードphp-equal-test.tgz


補足しておくと、PHPには===演算子というのがあります。これは型が同じで値が等しい場合にのみtrueになる演算子です。2年前から僕の主張は「==はキモくてバグのもとだから===使おうよ」です。


本記事の内容は僕の中では今更な内容ですし、他の方も何度も言及している内容ではあるんですけど、「初めて知ったよ!」というPHPの人も居るんじゃないでしょうか。


また、今回の話題は他の言語の人にとっては衝撃的な内容ではないでしょうか。round関数の話題と違って、これは言語のコアと言って良い場所のビミョーな点ですから、PHPDISりたい人たちにはナイスな話題かもしれませんね。(なんて煽ってみたりして)


参考リンク

マニュアル以外は全部自分の記事へのリンクです。PHPの==演算子関連でこんなに記事を書いているとは、自分でもビックリです。

通りすがり通りすがり 2008/06/07 12:05 ==はバージョンによっての解釈の違いはないんでしたっけ?

hnwhnw 2008/06/07 17:18 おっしゃる通り、細かいレベルでの差はありますが、ほぼ一緒と言っていいと思います。上の結果はPHP5.2.6での結果です。私の手元の環境で実験したところでは、PHP5.2.1以降で上の結果と同じ結果になるようです。

PHP5.2.1以前と以降とで結果が異なる理由は、数値文字形式の判定が微妙に修正された影響です。これはis_numeric(”1e-1000”)の結果が変わっていることからもわかります(PHP5.2.0以前は”1e-1000”は数値文字形式ではありませんでした)。この点を除けば、私の知る限りPHP5.0.3〜PHP5.2.6まで挙動は変わっていません。

PHP5.0.0からPHP5.0.2までは==演算子の解釈に関わるあたりにもバグフィックスが頻繁に入っていたようで、結構挙動が違います。このあたりのバージョンは使わない方がいいですよ、ということでしょう。

hnwhnw 2008/06/08 13:22 すみません、もう1点PHP5.2.1で修正されていました。数値文字形式同士の比較に関して、16進数表現と浮動小数点数表現とを比較するとPHP5.2.0以前では16進数表現が0.0扱いになっていたのがPHP5.2.1以降ではマトモになっています。PHP5.2.0以前は”0x1”==”1”なのに”0x1”==”0.0”でした。

通りすがり通りすがり 2008/06/09 00:33 まだバージョン4を使ってる人はまた挙動が違うかも?

hnwhnw 2008/06/10 00:36 PHP5系は確認用に全バージョン手元にあるんですが、PHP4は興味が無いので持ってないんですよね。そもそもPHP4系を新規プロジェクトに投入しなくなってもう2年以上になりますし…。

で終わるのも酷いのでPHP4.4.8を探してきました。結果、PHP5.0.3〜PHP5.2.0と全く同じ結果でした。それ以前のバージョンでどうか、などは残念ながら私にはわかりません。

mmmm 2008/06/26 17:09 http://d.hatena.ne.jp/hnw/20070524 でも、コメントを書いたものです。
推移律が成り立たない場所が…とありますが、たとえば、string(1) ” ” == int(0) かつ string(1) ” ” == bool(true) ならば int(0) == bool(true) のはずなのに、成り立っていないじゃないかということでしょうか。
そういう意味ならば、別にそうであって不思議ではありません。(数の同値関係などとは異なり、そもそもPHPの == で推移律が成り立たなければならない理由は、どこにもありません。推移律が成り立たない関係は、世の中にたくさんあります。)
このあたり、きちんととらえておかないと、深みにはまります。PHPの==は、型変換の考え方が間に入っているので、それをふまえれば、別段と不思議なことは何もないように思います。(PHPのマニュアル通り。)

hnwhnw 2008/06/26 19:29 これまた言葉足らずですみません。型を意識しないといけないこと自体が気持ち悪いなあ、というのが僕の主張です。一応記事のアップ前に読み直しているつもりなんですけど、もう一言書くべきでしたね。

さらに補足しておきますと、実験したら上のようになりました、不思議ですよね、というところで僕の理解が止まっているわけではありません。僕は==演算子の部分のソースコードを読んでいますし、挙動について完全に理解しているつもりです。ただ、上の表の挙動がソラで言える人は世の中にそう多くないんじゃないかな、とも思いますし、マニュアルに漏れなく完全に書いてあるわけでもありませんから、それだけPHPの==の挙動が複雑だ、ということだと思います。

そして、その複雑性が避けられないものかと言うと違うと思うんですね。Perlであれば==演算子は整数での比較演算子、eq演算子は文字列での比較演算子、ということで、どちらも推移律は成り立つわけです。比較演算子では推移律が成り立っていた方が覚えることが少なく、混乱も少ないのではないかと僕は個人的には思います。

まあ、「Perlが羨ましいなあ」といったモヤモヤした気持ちの方が強いかもしれません。PerlはPerlで比較演算子が2種類あることを知らない初心者がバグを入れる、といったトラブルがあるのかもしれませんけど、少なくとも僕にとっては理解しやすいですね。

mmmm 2008/06/26 21:33 なるほど、そういうモヤモヤとしたお気持ちが…。それは、わかる気がします。
たしかに、PHPではある種の便利さを優先しているために、結果として複雑な様相をみせるときがあります。
記事で触れている通り、==は「両辺の値の型をテキトーに合わせて比較する」(そして、なるべく数値に変換しようとする)というもので、それはそれで慣れると快適なこともあるのですけれどね…。慣れというのはオソロシイもので、気持ち悪いといわれれば、なるほどと気付くこともあります。

#今日ここに始めてきて、コメントさせていただきましたが、その後、hnwさんのいくつかの記事をみていて、よくよく深く考えているということは察していました。洞察が浅いかのように受け取れるコメントをしてしまい、たいへん失礼いたしました。

hnwhnw 2008/07/08 19:06 いえいえ、僕もムキになっていたところがあって、お恥ずかしいです(自分で挑発的なタイトルを付けたくせに…)。キモいというのは正直な感想なんですけど、キモい点があることを根拠にPHPを叩きたいわけではありません。むしろ、どうPHPと付き合っていくかの議論をしたいと考えています。

また、PHPの挙動に関して正確な理解をしていない人が居るようなら、広報(or啓蒙)していくことが大切だと僕は思っていますし、注目されやすい記事タイトルをつけるよう心がけています。一方で、タイトルを見てムッとされた方もいらしたような気がして(mmさんがそうだったのかはわかりませんが)、その点は反省材料ですかね…。

 | 
ページビュー
1884153