四捨五入
任意の桁数で完璧に四捨五入する
Pythonで四捨五入をするにあたって一般的にはround関数を使うと思います。
しかしround関数は偶数丸めであるため意図した結果にならないことがあります。
偶数丸めが四捨五入と違う点は以下です。
- 0.5は1ではなく0に、2.5は3ではなく2になります。
- 一般化して言うと、「偶数」.5の場合、切り上げた奇数ではなく、切り捨てた偶数になります。
例
l = [round(i+0.5) for i in range(10)]
print(l)
# 結果 [0, 2, 2, 4, 4, 6, 6, 8, 8, 10]
また、浮動小数点誤差の問題もあります。
二進数で表せる0.25(=1/4)や0.75(=3/4)は偶数丸めになっていますが、他は二進数で正確に表せないため規則性なく丸められています。
l = [round(i, 1) for i in [.05, .15, .25, .35, .45, .55, .65, .75, .85, .95]]
print(l)
# 結果 [0.1, 0.1, 0.2, 0.3, 0.5, 0.6, 0.7, 0.8, 0.8, 0.9]
次のようにすれば完璧な四捨五入が可能です。
from decimal import Decimal, ROUND_HALF_UP
def round_half_up(number, ndigits=0):
decimal_number = Decimal(str(number)).quantize(Decimal('10') ** -ndigits, rounding=ROUND_HALF_UP)
rounded_number = float(decimal_number) if ndigits > 0 else int(decimal_number)
return rounded_number
l = [round_half_up(i+0.5) for i in range(10)]
print(l)
# 結果 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
l = [round_half_up(i, 1) for i in [.05, .15, .25, .35, .45, .55, .65, .75, .85, .95]]
print(l)
# 結果 [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
ポイント
- roundと同じ感覚で使えるようにしています。
第二引数で桁数を指定します。
第二引数なしでも動作し、その場合1の位で四捨五入します。
第二引数>0の場合float型、<=0の場合int型で返します。(第二引数なしの場合もintです。) - 一旦数値を文字列にして、Decimal.quantizeすることで、浮動小数点による誤差を防いでいます。
- quantizeの中身を「10のマイナスndigits乗」にすることで、任意の桁数に丸められるようにしています。
「10のマイナスndigits乗」ではなく「0.1のndigits乗」にすると、0.1の0乗=1.0とか、0.1のマイナス1乗=10.0になってしまいます。いずれも「.0」があるため小数第一位で丸められてしまうのでダメです。
また、0.1自体浮動小数点誤差を含むため不適です。
Decimal('0.1')**ndigitsにすれば大丈夫そうですがまあこのままにします。
print(10**1) # 結果 10 (OK)
print(10**-1) # 結果 0.1 (OK)
print(0.1**1) # 結果 0.1 (OK)
print(0.1**-1) # 結果 10.0 (整数ではなく少数になっている、この場合小数第一位で丸められる)
print(0.1**-2) # 結果 99.99999999999999(浮動小数点誤差のため100にならない)
print(Decimal('0.1') **-1) # 結果 1E+1
問題あれば教えてください。