round(0.5)は、1か0か?


2016年 06月 07日

ちょっとNumpyで、浮動小数点数をaroundを使って丸めてみよう。

以下のようにして、0.5から1刻みで10個のちょうど中間になる値を用意した。 そして、np.aroundを用いて、一気に丸めてみた。
In [182]: np.arange((10))+0.5
Out[182]: array([ 0.5,  1.5,  2.5,  3.5,  4.5,  5.5,  6.5,  7.5,  8.5,  9.5])

In [183]: np.around(np.arange((10))+0.5)
Out[183]: array([  0.,   2.,   2.,   4.,   4.,   6.,   6.,   8.,   8.,  10.])
すると、
array([  1.,   2.,   3.,   4.,   5.,   6.,   7.,   8.,   9.,  10.])
になると思ったが、違った。 ここで、さらに、Python3 の丸め roundでも試してみた。
In [188]: round(2.5)
Out[188]: 2

In [189]: round(3.5)
Out[189]: 4

In [190]: round(4.5)
Out[190]: 4

In [191]: round(5.5)
Out[191]: 6
つまり、小数部が .5 の場合、切り上げまたは切り捨てのいずれかをするのではなくて、偶数にしてしまうのだ。実際、マニュアルにもそう書かれている。 確かに、この場合、問題になることは少ないから、これでもいいとは思うが、もうちょっとちゃんと調べてみた。

浮動小数点数をどのように処理するかについては、IEEE 754(IEEE 浮動小数点数演算標準)で詳しく規定されていて、ハードウェアレベルで実装されていることが多い。 この中では、丸めは最近接偶数への丸めが推奨されている。小数部が.5になったとき、つねに切り上げ、または切り下げを行っていると値に偏りが出てくるという考え方である。これを無くする、軽減するには、小数部が0.5のとき、1/2の確率で切り上げと切り下げになると都合が良い。そういう考え方で、この方法が最近は増えているようなのだ。

この丸め方を、偶数丸め、五捨五入とか、銀行丸めというのだそうだ。
銀行の場合、円以下の処理方法によって、処理件数が多ければかなり影響が出るかな。

この問題、Python, Numpyだけの問題ではなく、あらゆるプログラミング言語でroundをするとき発生する問題である。
これからは、roundは、小数部が0.5のとき切り上げされるとは限らないので、どう処理されても困らないように書いておこう。
細かい問題だけれど、気が付かないとハマってしまいそうだ。