Pythonのround関数にバグらしきものを見つけたよ、という報告です。下記は僕のMacBookでの実行結果です。
$ python -c 'x=9007199254740991.0; print "%.19f\n%.19f" % (x, round(x))' 9007199254740991.0000000000000000000 9007199254740992.0000000000000000000
xは9000兆より少し大きい整数で、IEEE754倍精度浮動小数点数で誤差無く表現できる数です。ところが、これをround関数で丸めたら1大きい数になってしまいました。
再現方法
今回も微妙な話題なので、環境によって起きたり起こらなかったりします。私の手元の環境で言うと、MacOSXとFreeBSDで起こり、Linux環境では起こりませんでした。コンパイルオプションによってはLinux環境でも同じ現象が観察できるかもしれません。
原因
整数を丸めて別の整数になるというのは奇妙な気がしますが、この原因はPythonのround関数の実装にあります。Pythonの実装ではこの場合のroundをfloor(x+0.5)で求めるのですが、今回のxについて0.5を足してしまうと9007199254740991.5となり、この数はdoubleでピッタリ表現できません。そこで一番近い数に丸めようとして偶数丸めが起こり、9007199254740992.0になるというわけです。
4503599627370497.0から9007199254740991.0までの奇数は全て同様の条件を満たすので、これらの数はPythonのround関数で丸めると1大きくなります。
実は今回の例は、直前のエントリ「Rubyのround関数の実装が変わってた」でも紹介しているのですが、Rubyの1.8.6-p114以前やPHPの5.2.6以前でも同じ現象が見られます。原因も全く同じで、これらのバージョンでは引数に0.5を足す実装だったためです。