hnwの日記

PHPの新しいround関数にバグをみつけた

PHP 5.3.0以降、PHPのround関数の挙動はChristian Seilerさんが提案したRFCに従って一新されています。この新しいround関数の仕様上の問題もしくはバグを見つけました。


以前の記事「PHP5.3.0alpha3のround関数の実装がPHP5.2.6と変わった」でこの新しい実装は「今までよりは良い実装」ではないかと書きましたが、撤回すべきかもしれません。ベースのアイデアとしては以前の記事にも書いた通り悪くないと思うのですが、考え漏れがあるような気がしています。


たとえば次のサンプルコードを見て下さい。

<?php
ini_set("precision",19);
var_dump(1000000000000000.125);
var_dump(round(1000000000000000.125));


サンプルコード中の1000000000000000.125という数はIEEE754倍精度でピッタリ表現できる数です。これをPHP 5.3.6で実行すると次の結果が返ります。

float(1000000000000000.125)
float(1000000000000000.125)


丸めたつもりが全然丸まっていないようです。


これはありがちな浮動小数点数の精度の問題ではありません。round関数が返すべき値は1000000000000000であり、これもまたIEEE754でピッタリ表せる数です。


実際、同じプログラムをPHP 5.2.17で実行すると次のようになります。

float(1000000000000000.125)
float(1000000000000000)


普通に考えれば、roundが整数を返さないのはおかしいですよね。


この問題は1000000000000000.125から4503599627370495.5までの範囲の小数について発生します。こうした大きさになると、浮動小数点数で表現できる数を数直線にプロットした場合に0.125刻みや0.5刻みといった粒度になっているので、精度としてギリギリの数であるのは確かです。


新しいround関数は、浮動小数点数を10進小数のつもりで扱っているユーザーが混乱しないような仕様を目指したものだと考えています。その仕様に考え漏れがあった、ということでしょう。


発生する条件がそれなりに限定的なので、今のところ誰も困っていないだろうとは思いますが、やっぱり仕様はシンプルに保つべきだよなー、なんて思ってしまいます。


対案もなしに現象だけバグ報告してみたらbogus扱いされてしまった(PHP :: Bug #54479)ので、カッとなってブログ記事にしてみました。僕は以前の仕様に戻すくらいしか対案が無いのですが、今更そんな話が通るとも思えないので、どなたか良いアイデアがあれば教えてください。