前回のあらすじ:
Pythonのビルトインのround()
は普通の四捨五入とは違う偶数丸めというものだったので、floor()
を使って普通の四捨五入を実装したよ。
誤差との戦い
と、まぁ普通はここまでなんだけど、いろいろ試すと「あれ?」ってなるケースが出てくる。
具体的には、1.255を四捨五入して小数第二位までにしようとしたとき。 普通に考えれば小数第三位の数字が5なので切り上げられて1.26になると思うけど、残念ながらそうはならない。
myround2(1.255, 2) # => 1.25
ちなみに偶数丸めするround()ならこれは1.26になるはずなんだけど、試したらこうだった:
round(1.255, 2) # => 1.25
Pythonくんさぁ・・・
それはともかく、どうしてこうなるのかというと、誤差の関係。 浮動小数点数だと10進数の数字は正確にもてない場合があって、その場合に計算するといろいろ誤差が出てしまう。
これは実際に1.255 * 100
を計算してみるとよく分かる:
1.255 * 100 # => 125.49999999999999
見てのとおり、きっちり125.5になっていなくて125.499999...となってしまっている。 このせいで0.5を足しても126に上がってくれなくて、結果1.26ではなく1.25となっていた。
まぁ、誤差が原因だとしょうがない感じはある。 きっと他のプログラミング言語でも同様の制約は出るはずだし。
と思って試しにRubyでやってみた結果がこれ:
1.255.round(2) # => 1.26
えっ、ちゃんと四捨五入できてる? なんで???
ちなみに、もちろんRubyでも内部では誤差を持ってて、1.255 * 100
を計算してみるとこうなる:
1.255 * 100 # => 125.49999999999999
じゃあ、Rubyは一体どうやって四捨五入を行なっているのか。 それについてはまたあとで言及したい。
PyPIのパッケージ
まぁ何はともあれ、四捨五入が思ったよりも大変そうということが分かったので、だったら誰かがパッケージを作って公開してるだろうとPyPIを検索。 すると、案の定いくつかのパッケージが見つかった:
最初に言っておくと、これらのパッケージはダメダメなので使ったらダメ。 (宣伝になってしまうけど、これらを使うくらいなら拙作のsaneroundを使って・・・)
実際、試してみた結果がこれ:
まずはround2。
from round2 import round2 round2(0.5) # => 1, OK round2(1.5) # => 2, OK round2(-0.5) # => -1, OK round2(-1.5) # => -2, OK round2(15, -1) # => nan, NG round2(25, -1) # => nan, NG round2(1.255, 2) # => 1.25, NG
見てのとおり、四捨五入の桁で負の数は未サポート。 また、1.255の小数第三位を四捨五入しても1.26にはならずに1.25になってしまってる。
次にmath-round。
from math_round import mround mround(0.5) # => 1, OK mround(1.5) # => 2, OK mround(-0.5) # => 0, NG mround(-1.5) # => -1, NG mround(-2.0) # => -1, NG(根本的な問題がありそう) mround(15, -1) # => 20.0, OK mround(25, -1) # => 30.0, OK mround(1.255, 2) # => 1.25, NG
こちらは負の数を四捨五入したときの挙動がおかしい。 単に絶対値で扱ってないことによる問題かと思いきや、-2を四捨五入すると-1になってるあたり、根本的な問題がありそう。 そして当然1.255の四捨五入結果は1.25。
最後にmath-round-af。
from math_round_af import get_rounded_number get_rounded_number(0.5) # => 1.0, OK get_rounded_number(1.5) # => 2.0, OK get_rounded_number(-0.5) # => -1.0, OK get_rounded_number(-1.5) # => -2.0, OK get_rounded_number(15, -1) # => ValueError, NG get_rounded_number(25, -1) # => ValueError, NG get_rounded_number(1.255, 2) # => 1.25, NG
こちらも四捨五入の桁で負の数は未サポートとなっている。 また、1.255の四捨五入結果はやっぱり1.25。
とまぁこんな感じで、精度の問題どころか、基本的な実装すらできてないお粗末なパッケージしかなくて、本当にダメダメ。 3つも引っ張ってくりゃ普通は1つくらいまともなパッケージありそうなもんだけど。。。
もちろん、ちゃんと探せばもしかしたらまともなパッケージがまだあるかもしれない。 でも、これはもう自分でなんとかした方が早そうだなとなった。
ということで、次はRubyの実装を調査していくことになる。
今日はここまで!