hnwの日記

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の話題です。これまでPHPx86アーキテクチャの一部環境(おそらく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まで放置すればいいだけだと思うんですがね。


興味を持った奇特な方はPHPソースコードのext/standard/math.cを見てみてください。