はじめに
こんなコードがある。
for i in range(10): num = i+0.5 print("round(" + str(num) + ") = " + str(round(num)))
これは、0.5から9.5までの値を四捨五入して出力するだけの関数である。
round関数は、引数の値を四捨五入する関数。
これをpython2系とpython3系でそれぞれ実行すると、以下の出力が得られる。
python2系
[shell] round(0.5) = 1.0round(1.5) = 2.0
round(2.5) = 3.0
round(3.5) = 4.0
round(4.5) = 5.0
round(5.5) = 6.0
round(6.5) = 7.0
round(7.5) = 8.0
round(8.5) = 9.0
round(9.5) = 10.0
[/shell]
python3系
[shell] round(0.5) = 0round(1.5) = 2
round(2.5) = 2
round(3.5) = 4
round(4.5) = 4
round(5.5) = 6
round(6.5) = 6
round(7.5) = 8
round(8.5) = 8
round(9.5) = 10
[/shell]
あれっ…Python2系と3系で出力が違う…。特にPython3系は思ってたのと違ってなんだかハマりそう。。
結論から言えば、これはバグでもなんでもなくて仕様です。この仕様についてちょっと調べたのでまとめておきます。
環境
- Python2系: 2.7.15
- Python3系: 3.7.0
(それぞれ、2018.9.21現在、pyenvで構築できるそれぞれの最新の安定バージョン)
仕様について
わからんので、とりあえず公式マニュアルを参照してみる。
Python2系(2.7)でのround関数の説明
Python3系(3.6)でのround関数の説明
うん。英語があんまり得意ではないので、完璧には原理を理解できませんが…小数点を2進数で示した際に正確に表す事ができない事が原因のようです。それぞれの説明のあとにNoteとしPythonの浮動小数点演算、その問題と制限を参照してくださいと書いてあります。こちらを参照すると、まぁよくある 0.1+0.1+0.1 != 0.3 みたいな話が書いてあります。
なぜ、Python2系でもほぼ同様の話がなされていますが…round関数の仕様からなぜ2系と3系で出力が異なるのかといったことに対する結論は見つけられませんでした。。。
解決策(案)
roundの仕様から正確な原因はわかりませんが、このままでは困る事がありそうなので解決策を示しておきたいと思います。
小数点以下1桁を四捨五入した際の0.5の扱いが怪しい…ということで、0.5を加算したあとに小数点以下を切り捨てする事で期待する動作をpython2系でも3系でも実現することができました。
import math for i in range(10): num = i+0.5 print("round(" + str(num) + ") = " + str(math.floor(num+0.5))) # num+0.5 : 0.5を加算... 0.5=>1.0, 1.5=>2.0 みたいな。 # math.floor : 切り上げ
Python2系
[shell] round(0.5) = 1.0round(1.5) = 2.0
round(2.5) = 3.0
round(3.5) = 4.0
round(4.5) = 5.0
round(5.5) = 6.0
round(6.5) = 7.0
round(7.5) = 8.0
round(8.5) = 9.0
round(9.5) = 10.0
[/shell]
Python3系
[shell] round(0.5) = 1round(1.5) = 2
round(2.5) = 3
round(3.5) = 4
round(4.5) = 5
round(5.5) = 6
round(6.5) = 7
round(7.5) = 8
round(8.5) = 9
round(9.5) = 10
[/shell]
うぬ。期待していた出力が出てきてくれた気がする。よしよし。
ついでに、python3系ではround関数の引数に桁数を指定しないと少数第一位を四捨五入して整数にまとめるのですが、第二引数に0を指定すると戻り値が少数になってpython2系と同様になります。
round(1.5) # 戻り値は 2 round(1.5,0) # 戻り値は 2.0
まとめ
う〜む。あんまり爽快じゃないですが…とりあえず解決って感じです。Pythonに強い方がおられましたら2系と3系で異なる理由を教えてもらえると嬉しいです。とりあえず解決ってことで!