Pythonに組み込みのround関数による数値の丸め方とその注意点、Decimalクラス(10進実数)を使った数値の四捨五入のやり方を紹介する。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
* 本稿は2021年4月6日に公開された記事をPython 3.12.2で動作確認したものです(確認日:2024年3月1日)。
# round関数
n = 0.123456
m = round(n)
print(m) # 0
m = round(n, 2) # 第2引数は小数点以下第x位(10-xの桁)で丸めるかの指定
print(m) # 0.12
n = 123.456
m = round(n, -2) # 10-1×-2=102の桁で丸める
print(m) # 100.0
# 注意点
n = 1.5
m = round(n) # 1.5を小数点以下で四捨五入した結果は2
print(m) # 2
n = 0.5
m = round(n) # 0.5を小数点以下で四捨五入した結果は0?
print(m) # 0
n = 3.15 # 3.15 = 3.149999999999999911182158029987476766109466552734375
m = round(n, 1) # 3.15を小数第1位に丸めた(小数第2位で四捨五入した)
print(m) # 3.1 # つもりだが、その結果は3.1?
# decimalモジュールのDecimalクラスとそのquantizeメソッド
from decimal import Decimal, getcontext, ROUND_HALF_UP
c = getcontext() # 現在の演算コンテキストを取得
c.rounding = ROUND_HALF_UP # 丸めモードとして四捨五入を指定
s = '123.123456'
d = Decimal(s)
print(d) # 123.123456
print(d + 1.0) # TypeError:Decimal値と浮動小数点数値は演算できない
print(float(d) + 1.0) # 124.123456:float型に型変換する必要がある
r = d.quantize(Decimal('0'), rounding=ROUND_HALF_UP) # 小数点以下を四捨五入
print(r) # 123
r = d.to_integral_value()
print(r) # 123
r = d.quantize(Decimal('0.0000')) # 小数第5位で四捨五入
print(r) # 123.1235
r = d.quantize(Decimal('1e-3')) # 小数第3位で四捨五入
print(r) # 123.123
r = d.quantize(Decimal('1e1')) # 整数部の1の位で四捨五入
print(r) # 1.2E+2
r = int(r)
print(r) # 120
Pythonには組み込みで数値の丸め(四捨五入)を行うためのround関数が用意されている。その構文は次の通りだ。
round(number[, ndigits])
numberには対象の数値を、ndigitsには丸めた後の結果の桁数を指定する。ndigitsに0を指定したり、省略したりしたときには「元の値に最も近い整数」が戻り値となる。Pythonのドキュメントによると、ndigitsに正値または負値を指定したときには、「値は 10 のマイナス ndigits 乗の倍数の中で最も近いものに丸められ」るとある。
以下に例を示す。
n = 0.123456
m = round(n) # nに最も近い整数
print(m) # 0
m = round(n, 2) # 小数点以下がndigits桁になるように丸める
print(m) # 0.12
n = 123.456
m = round(n, -2) # 10-1×-2=102の桁で丸める
print(m) # 100.0
最初の例はndigits=0なので、.123456という値に最も近い整数である0が戻り値となっている。2つ目の例ではndigits=2なので、10-2=0.01の倍数の中で0.123456に一番近い0.12(=0.01×12)へと丸められているということだ(これは小数「ndigits+1」位を四捨五入という処理に相当する)。3つ目の例なら10-1×-2=2=100の倍数の中で最も近い100に丸められている。
注意が必要なのは、ここでいう「倍数に最も近い値」は最大で2つある点だ。例えば、「round(1.5)」を考える。1.5に最も近い整数値としては「1」と「2」がある(どちらもその差は0.5)。このときには、round関数は偶数を選ぶようになっている(これを「偶数丸め」「銀行丸め」などと呼ぶ)。そのため、次のように予想とは異なる値が返されることがある。
n = 1.5
m = round(n) # nに最も近い「1」と「2」から偶数を選ぶ
print(m) # 2
n = 0.5
m = round(n) # nに最も近い「0」と「1」から偶数を選ぶ
print(m) # 0
これは小数の場合でも同様だ。
n = 0.75
m = round(n, 1) # 0.1の倍数で0.75に最も近い「0.7」と「0.8」から偶数を選ぶ
print(m) # 0.8
n = 0.25
m = round(n, 1) # 0.1の倍数で0.25に最も近い「0.2」と「0.3」から偶数を選ぶ
print(m) # 0.2
多くの場合、round関数は四捨五入に相当する振る舞いをするが、上のように想定とは異なる値が返ってくる場合があることには注意が必要だ。
もう一つ、Pythonの実数(浮動小数点数)は誤差を含んでいる点にも注意したい。以下に例を示す。
n = 3.15
print(f'{n:.20f}') # 3.14999999999999991118
m = round(3.15, 1)
print(m) # 3.1
「3.15」という浮動小数点数値は多くのPython実装で実際には「3.149999999999999911182158029987476766109466552734375」のような値となる。実質的には3.15とほぼ同値であるが、「round(3.15, 1)」としてこの値を小数点以下1桁に丸めようとする(小数第2位で四捨五入しようとする)と、3.2ではなく3.1と想定とは異なる値となってしまう。
これはコンピューター内部では実数の小数部は2-nの値の和として表現されていることから発生する(なお、上で見た0.5は2-1として、0.75は2-1+2-2として、0.25は2-2として表現できるので、幸いなことに誤差が出ないが、3.15など実数の多くは2進数で正しく表現できない)。
10進の実数演算を行うdecimalモジュールのDecimalクラスを使うことで、こうした問題を回避できる。
decimalクラスは10進の実数演算を行うためのモジュールだ。ここでは、このモジュールを使って四捨五入を行う方法だけを簡単に示す。
from decimal import Decimal, getcontext, ROUND_HALF_UP
c = getcontext() # 現在の演算コンテキストを取得
c.rounding = ROUND_HALF_UP # 丸めモードとして四捨五入を指定
s = '123.123456'
d = Decimal(s) # 浮動小数点数の文字列表現からDecimalクラスのインスタンスを生成
print(type(d), d) # <class 'decimal.Decimal'> 123.123456
decimalモジュールからインポートしているDecimalクラスは10進の実数演算をサポートするクラス、getcontextは現在の演算コンテキストを表すContextオブジェクトを取得するための関数、ROUND_HALF_UPはDecimalクラスのインスタンスを使って丸め演算を行う際の丸めモードを四捨五入に設定するための値だ。Contextオブジェクトでは、演算を行う際の精度や丸め方などを取得/設定できる。
インポートした後は、現在の演算コンテキストを取得して、そのrounding属性に一般的な(0.5未満を切り捨てて、0.5以上を切り上げる)四捨五入を表すROUND_HALF_UPを指定している。これにより、以降で行うquantizeメソッド呼び出し=丸め演算ではデフォルトで四捨五入が行われるようになる。先ほど出てきた「偶数丸め」を行うのであれば、decimalモジュールが提供するROUND_HALF_EVENを指定する。
これら以外に指定可能な値についてはPythonのドキュメント「丸めモード」を参照のこと(取得した現在の演算コンテキストのrounding属性に必ずしもROUND_HALF_UP値を設定する必要はないが、その場合にはquantizeメソッドの呼び出し時に引数roundingにこの値を指定する必要がある。ここではメソッド呼び出しが長くならないように、前もって設定をしている)。
Decimalクラスのインスタンスは浮動小数点数、整数、文字列などを基に生成できる(詳細についてはPythonのドキュメント「Decimal オブジェクト」を参照)。上の例では浮動小数点数の文字列表現('123.123456')を基にインスタンスを生成している。
気を付けたいのは、Decimalオブジェクトと浮動小数点数を含めた数値演算はできない点だ(整数値との演算は可能)。
print(d + 1.0) # TypeError:Decimal値と浮動小数点数値は演算できない
print(float(d) + 1.0) # 124.123456:float型に型変換する必要がある
よって、何らかの理由があって、浮動小数点数値をDecimal値に変換したときには、必要な処理が終わった時点で、その値を浮動小数点数値として使いたいのであれば、float関数を使って浮動小数点数値に変換する必要がある。
この値について小数点以下を四捨五入するには以下のようなコードを実行する。
Copyright© Digital Advantage Corp. All Rights Reserved.