Pythonでの小数点の扱いと四捨五入

Pythonで四捨五入をする

Pythonを知れば知るほどそのすごさに驚かせられますが、四捨五入については少し面倒なことになります。このあたりを深堀してみます。

組み込み関数roundによる四捨五入

事務計算における割り算では、四捨五入をするのが一般的です。Pythonにはround関数が組込関数として用意されているので、エクセルと同じように計算してみます。

 #1 組み込み関数roundによる四捨五入(整数表示)
print(round(0.5, 0),end=" , ")
print(round(1.5, 0),end=" , ")
print(round(2.5, 0),end=" , ")
print(round(3.5, 0),end=" , ")
print(round(4.5, 0),end=" , ")
print(round(5.5, 0),end=" , ")
print(round(6.5, 0),end=" , ")
print(round(7.5, 0),end=" , ")
print(round(8.5, 0),end=" , ")
print(round(9.5, 0))
0.0 , 2.0 , 2.0 , 4.0 , 4.0 , 6.0 , 6.0 , 8.0 , 8.0 , 10.0

このように、0.5から9.5までにつき小数点以下を四捨五入し整数で表示をすると、2.5、が2にというように小数点以下が5であるにもかかわらず切り捨てられてしまいます。これはJIS丸め(銀行丸め)といわれる方法で、小数点第2位がぴったり5となって割り切れる場合には、整数部分は偶数の方に丸めるようにします。例えば次のような構成比を計算するときに、JIS丸めの方が構成比の合計が100%になる確率が高くなると考えられます。

エクセルによる四捨五入とJIS丸の計算

ところが、これが小数点第1位までを計算すると、これまた上手く行きません。

#2 組み込み関数roundによる四捨五入(小数点表示)
print(round(0.05, 1),end=" , ")
print(round(0.15, 1),end=" , ")
print(round(0.25, 1),end=" , ")
print(round(0.35, 1),end=" , ")
print(round(0.45, 1),end=" , ")
print(round(0.55, 1),end=" , ")
print(round(0.65, 1),end=" , ")
print(round(0.75, 1),end=" , ")
print(round(0.85, 1),end=" , ")
print(round(0.95, 1))
0.1 , 0.1 , 0.2 , 0.3 , 0.5 , 0.6 , 0.7 , 0.8 , 0.8 , 0.9

そこでPythonのドキュメントを調べると、round関数に次のよう記述があります。

はじめから難しいことが書いてあります。一つ一つ解明していきます。まず、別表第一とは次のとおりです。

浮動小数点数に対するround()の振る舞いは意外なものかもしれません:例えば、round(2.675,2)は予想通りの2.68ではなく2.67を与えます。これはバグではありません:これはほとんどの小数が浮動小数点数で正確に表せないことの結果です。

https://docs.python.jp/3.3/library/functions.html#round

Decimalモジュールによる四捨五入

Decimalモジュールによる計算

どうやら、Pythonのround関数は、学校で習った四捨五入をするためには2つの意味で使い物になりません。この場合にはdecimalモジュールを使う必要がありそうです。Decimalには四捨五入したい値または式をクオーテーション付きで入力し、quantizeの中は表示したい小数点、roundingには次の四捨五入のオプションを入力します。

Decimalモジュール

Decimal(四捨五入したい値又は式).quantize(Decimal(桁数を示す形式),rounding=四捨五入の方法)

roundingの四捨五入の方法は次のとおりです。

ROUND_CEILING Infinity方向に丸めます。

ROUND_DOWN

ゼロ方向に丸めます

ROUND_FLOOR

Infinity方向に丸めます

ROUND_HALF_DOWN

近い方に、引き分けはゼロ方向に向けて丸めます。

ROUND_HALF_EVEN

近い方に、引き分けは偶数整数方向に向けて丸めます

ROUND_HALF_UP

近い方に、引き分けはゼロから遠い方向に向けて丸めます

ROUND_UP

ゼロから遠い方向に丸めます

ROUND_05UP

ゼロ方向に丸めたあとの最後の桁が0または5ならばゼロから遠い方向に、そうでなければゼロ方向に丸めます

https://docs.python.jp/3/library/decimal.html

いわゆる四捨五入

通常の四捨五入をするときには、” ROUND_HALF_UP”を使うといまくいくようです。

#3 decimalモジュール quantize メソッド(ROUND_HALF_UP)を使った四捨五入

from decimal import *

print(Decimal('0.5').quantize(Decimal('1'),
      rounding=ROUND_HALF_UP),end=" , ")
print(Decimal('1.5').quantize(Decimal('1'),
      rounding=ROUND_HALF_UP),end=" , ")
print(Decimal('2.5').quantize(Decimal('1'),
      rounding=ROUND_HALF_UP),end=" , ")
print(Decimal('3.5').quantize(Decimal('1'),
      rounding=ROUND_HALF_UP),end=" , ")
print(Decimal('4.5').quantize(Decimal('1'),
      rounding=ROUND_HALF_UP),end=" , ")
print(Decimal('5.5').quantize(Decimal('1'),
      rounding=ROUND_HALF_UP),end=" , ")
print(Decimal('6.5').quantize(Decimal('1'),
      rounding=ROUND_HALF_UP),end=" , ")
print(Decimal('7.5').quantize(Decimal('1'),
      rounding=ROUND_HALF_UP),end=" , ")
print(Decimal('8.5').quantize(Decimal('1'),
      rounding=ROUND_HALF_UP),end=" , ")
print(Decimal('9.5').quantize(Decimal('1'),
      rounding=ROUND_HALF_UP))

1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10

decimalモジュールによるJIS丸めの計算(整数表示)

JIS丸めにするときにはroundingでROUND_HALF_UPを指定します。こちらも小数点第1まで計算する場合もうまくいきました。もちろん計算結果に式を使っても結果は同じです。

1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10
#4 decimalモジュール quantizeメソッド(ROUND_HALF_EVEN)を使ったJIS丸め

from decimal import *

print(Decimal('0.05').quantize(Decimal('0.1'),
      rounding=ROUND_HALF_EVEN),end=" , ")
print(Decimal('0.15').quantize(Decimal('0.1'),
      rounding=ROUND_HALF_EVEN),end=" , ")
print(Decimal('0.25').quantize(Decimal('0.1'),
      rounding=ROUND_HALF_EVEN),end=" , ")
print(Decimal('0.35').quantize(Decimal('0.1'),
      rounding=ROUND_HALF_EVEN),end=" , ")
print(Decimal('0.45').quantize(Decimal('0.1'),
      rounding=ROUND_HALF_EVEN),end=" , ")
print(Decimal('0.55').quantize(Decimal('0.1'),
      rounding=ROUND_HALF_EVEN),end=" , ")
print(Decimal('0.65').quantize(Decimal('0.1'),
      rounding=ROUND_HALF_EVEN),end=" , ")
print(Decimal('0.75').quantize(Decimal('0.1'),
      rounding=ROUND_HALF_EVEN),end=" , ")
print(Decimal('0.85').quantize(Decimal('0.1'),
      rounding=ROUND_HALF_EVEN),end=" , ")
print(Decimal('0.95').quantize(Decimal('0.1'),
      rounding=ROUND_HALF_EVEN))
0.0 , 0.2 , 0.2 , 0.4 , 0.4 , 0.6 , 0.6 , 0.8 , 0.8 , 1.0