hnwの日記

PHP以外全員不正解

いまだにround関数のまとめは出来ていません。そろそろ皆様に忘れられる前にまとめなきゃな、とは思っていますが、もうしばらくお待ちください。今回は微妙にround関数と近いような遠いような話題です。


巨大な数の丸めについて実験しているうちに、PHPだけ正解でPerlRubyPythonが不正解と思われる例を見つけました。普段の叩かれっぷりを考えるとPHPだけ正解なんて状態は有り得ない気がするのですが、僕には他の結論が導けませんでした。識者の皆様に「ダウト!」と指摘して頂ければ幸いです。


今回は、2の65乗のことを265と表現することにします*1


265=36893488147419103232の次に大きいIEEE64bit浮動小数点数は265+213=36893488147419111424です。仮に、265+212+1=36893488147419107329を浮動小数点数に丸めるとしたら、IEEE754を持ち出すまでもなく表現できる中で最も近い浮動小数点数に丸めるのが自然でしょうから、265+213になるはずです。では実際に試してみましょう。

$ php -r 'printf("%21.0f\n",36893488147419107329);'
 36893488147419111424
$ perl -e 'printf("%21.0f\n",36893488147419107329);'
 36893488147419103232
$ ruby -e 'printf("%21.0f\n",36893488147419107329);'
 36893488147419103232
$ python -c 'print "%21.0f\n" % 36893488147419107329;'
 36893488147419103232


PHPは期待通り265+213に丸めてくれましたが、PHP以外は全員265に丸めました。こんなことってあるんでしょうか。


RubyPythonに関して言うと、上の例では多倍長整数から浮動小数点数へのキャストをしていますから、浮動小数点数リテラルだとどうなるかも実験しておきましょう*2

$ php -r 'printf("%21.0f\n",36893488147419107329.0);'
 36893488147419111424
$ perl -e 'printf("%21.0f\n",36893488147419107329.0);'
 36893488147419103232
$ ruby -e 'printf("%21.0f\n",36893488147419107329.0);'
 36893488147419103232
$ python -c 'print "%21.0f" % 36893488147419107329.0;'
 36893488147419111424


PythonPHPと同じく265+213に丸めるようになりましたが、Rubyは相変わらず265に丸めます。ではCでも実験してみましょう。

#include <stdio.h>
#include <stdlib.h>

int main() {
  printf("%21.0f\n%21.0f\n",
         36893488147419107329.0,
         strtod("36893488147419107329", NULL));
  return 0;
}
$ gcc -O2 hoge.c; ./a.out
 36893488147419111424
 36893488147419111424


C言語浮動小数点数リテラルとして丸めるのと、strtod(3)を呼んだときと、どちらもPHPと同じく265+213に丸めるという結果になりました。


このあたりの挙動は各言語ともundocumentedな気もしますけど、Cに素直に渡せば勝手に丸めてくれるものを違う結果にしてしまっているわけですから、バグと言ってしまって構わない挙動だと言えます。


念のため環境も貼っておきます。

$ uname -mrs
Linux 2.6.18-8.el5xen i686
$ php5 -version
PHP 5.2.3 (cli) (built: Jul  6 2007 08:30:38)
Copyright (c) 1997-2007 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2007 Zend Technologies
$ perl --version

This is perl, v5.8.8 built for i386-linux-thread-multi

Copyright 1987-2006, Larry Wall

Perl may be copied only under the terms of either the Artistic License or the
GNU General Public License, which may be found in the Perl 5 source kit.

Complete documentation for Perl, including FAQ lists, should be found on
this system using "man perl" or "perldoc perl".  If you have access to the
Internet, point your browser at http://www.perl.org/, the Perl Home Page.

$ ruby -v
ruby 1.8.5 (2006-08-25) [i386-linux]
$ python -V
Python 2.4.3
$ gcc --version
gcc (GCC) 4.1.1 20070105 (Red Hat 4.1.1-52)
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.


今回は何が言いたいかというと、この奥村先生のコメントに対して「そもそもプロがスクリプト言語数値計算するわけがないでしょう?」ということです。残念ながら各スクリプト言語gccや商用Cコンパイラに比べて浮動小数点数まわりで叩かれ足りていないと予想できます。鶏と卵の関係ですけど、信用が無いから使われない、使われないから細かいバグが直らない、という状況かと思います。仕事で浮動小数点数を使うことは滅多に無い僕程度のアマチュアに粗探しされてしまうのも、各スクリプト言語浮動小数点数まわりに関して叩かれ足りていない証拠ではないでしょうか。


というわけで、PHPのround関数の実装の話に関して言えば、「プロが使えない」というのは問題点の指摘としては全くズレています。浮動小数点数のプロがどんな人種かハッキリとはわかりませんけど、CとFORTRAN以外信用していない人種なんじゃないですかね?これで困る人って、自分が詳しくもない言語を中身も見ないで使って、かつ浮動小数点数の細かい挙動が重要な人ってことですよね。それはどんなプロなんでしょうか。

*1:tex記法と、2^65のような記法と今まで試してきたんですが、どうも不評なので…

*2:PHPPerlについてはどちらでも同じ意味のはずですが、念のため実験しています