PHP5.2.7のround関数の実装もPHP5.2.6と変わった
題名の通り、PHP5.2.7でround関数の実装が変更されました。12月8日付でPHP5.2.8がリリースされましたが、round関数についてはPHP5.2.7と同一です。
概要
ますがたさんの記事で既報ですが、bugs #42294に関してPHP5.2.7で修正が入り、round関数の実装が変わりました。
PHP5.2.7の実装をPHP5.2.6の実装と比較すると、2点変更が入っています。
- いつでも丸めの基準が0.50000000001になりました。
- 実装に変更がありました。丸め結果に影響するかもしれません。
先日の記事「PHP5.3.0alpha3のround関数の実装がPHP5.2.6と変わった」の通りPHP5.3.0からは更に別の実装が採用されそうですから、この変更はPHP5.2.7から5.2.xまでの期間限定になりそうです。
丸めの基準が0.50000000001になった
これは例のPHP_ROUND_FUZZの話題です。これまでPHPはx86アーキテクチャの一部環境(おそらくLinuxのみ)で丸めの基準を0.50000000001にしていましたが、PHP5.2.7では全環境で0.50000000001になりました。
僕の手元のMacOSX環境の例を示します。
$ php-5.2.6 -r 'var_dump(round(0.49999999999));' float(0) $ php-5.2.7 -r 'var_dump(round(0.49999999999));' float(1)
いまさら変更すんなよ!って気もしますが、PHPですしね。
実装の変更
PHP5.2.7では、FreeBSDのroundの実装を参考に、これまでとroundの計算方法が変わっています。
参考にしたと言ってもC99のround関数には第2引数は無いので、FreeBSDのコードをベースに、丸めの桁数の処理を追加し、元のソースコードで0.5だったものを0.50000000001に変更しています。
正の整数xの四捨五入round(x)を例にすると、これまでは下記のように計算していました。
- floor( x + 0.50000000001 )
PHP5.2.7では次のように計算します。
- if (ceil(x) - x > 0.50000000001)
- 真ならfloor(x)
- 偽ならceil(x)
小数点以下第n位で丸める場合には、xに10^nを掛けてから上の計算を行います。
理屈上は、一部のレアケースを除いて両者に差は無いはずだと僕は考えています*1。ただ、実際に動かしてみると僕の環境では違いが見られました。下記は、元々PHP_ROUND_FUZZが有効になっていたLinux環境です。
$ php-5.2.6 -r 'var_dump(round(0.049999999999,1));' float(0) $ php-5.2.7 -r 'var_dump(round(0.049999999999,1));' float(0.1)
原因としては、PHP5.2.6まではマクロで実現していたものをPHP5.2.7では関数で実装したことが考えられます。PHP5.2.6では80bit浮動小数レジスタを利用して高い精度で計算していたものが、PHP5.2.7では計算の途中結果を変数に格納しているため、64bit精度に落ちた可能性があります。そうだとすれば、この結果も最適化オプション次第で変わるのかもしれません。
感想など
なんでこのタイミングで変更するかなあ?というのが正直な感想です。PHPの中の人はあまり浮動小数点数なんか興味がなさそうだし、無闇にいじらないのが賢明というものでしょう。今回のきっかけになったバグは1年放置してあったんだから、PHP5.3.0まで放置すればいいだけだと思うんですがね。
*1:「Pythonのround関数で奇数を丸めたら偶数になった」を併せてご覧下さい