hnwの日記

Pythonのround関数で奇数を丸めたら偶数になった

Pythonのround関数にバグらしきものを見つけたよ、という報告です。下記は僕のMacBookでの実行結果です。

$ python -c 'x=9007199254740991.0; print "%.19f\n%.19f" % (x, round(x))'
9007199254740991.0000000000000000000
9007199254740992.0000000000000000000


xは9000兆より少し大きい整数で、IEEE754倍精度浮動小数点数で誤差無く表現できる数です。ところが、これをround関数で丸めたら1大きい数になってしまいました。

再現方法

今回も微妙な話題なので、環境によって起きたり起こらなかったりします。私の手元の環境で言うと、MacOSXFreeBSDで起こり、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を足す実装だったためです。

続きを読む