Pythonで小数・整数を四捨五入するroundとDecimal.quantize
Pythonで数値(浮動小数点数float
や整数int
)を四捨五入や偶数への丸めで丸める方法について説明する。
組み込み関数round()
は一般的な四捨五入ではなく偶数への丸めなので注意。一般的な四捨五入を実現するには標準ライブラリのdecimalモジュールを使うか、新たな関数を定義する。
四捨五入ではなく小数点以下の切り捨て・切り上げについては以下の記事を参照。
NumPyやpandasのround()
については以下の記事を参照。
組み込み関数round()
組み込み関数としてround()
が提供されている。モジュールをインポートすることなく使える。
第一引数に元の数値、第二引数ndigits
に桁数(何桁に丸めるか)を指定する。
小数を任意の桁数に丸める
浮動小数点数float
に対してround()
を使う例を示す。
第二引数を省略すると整数に丸められる。型も整数int
になる。
f = 123.456
print(round(f))
# 123
print(type(round(f)))
# <class 'int'>
第二引数を指定すると浮動小数点数float
を返す。
正の整数を指定すると小数点以下の桁、負の整数を指定すると整数の桁(位)の指定となる。-1
は十の位、-2
は百の位に丸める。0
は整数(一の位)に丸めるが、省略した場合と異なりfloat
を返す。
print(round(f, 1))
# 123.5
print(round(f, 2))
# 123.46
print(round(f, -1))
# 120.0
print(round(f, -2))
# 100.0
print(round(f, 0))
# 123.0
print(type(round(f, 0)))
# <class 'float'>
整数を任意の桁数に丸める
整数int
に対してround()
を使う例を示す。
第二引数を省略した場合や0
、正の整数を指定した場合は元の値をそのまま返す。負の整数を指定すると対応する整数の桁に丸められる(-1
は十の位、-2
は百の位…)。いずれの場合も整数int
を返す。
i = 99518
print(round(i))
# 99518
print(round(i, 2))
# 99518
print(round(i, -1))
# 99520
print(round(i, -2))
# 99500
print(round(i, -3))
# 100000
round()は一般的な四捨五入ではなく、偶数への丸め
組み込み関数round()
による丸めは、一般的な四捨五入ではなく、偶数への丸め(銀行家の丸め、round to even)なので注意。
0.5
が0
、2.5
が2
に丸められたり、十の位に丸める場合に5
が0
、25
が20
に丸められたりする。
print('0.5 =>', round(0.5))
print('1.5 =>', round(1.5))
print('2.5 =>', round(2.5))
print('3.5 =>', round(3.5))
print('4.5 =>', round(4.5))
# 0.5 => 0
# 1.5 => 2
# 2.5 => 2
# 3.5 => 4
# 4.5 => 4
print(' 5 =>', round(5, -1))
print('15 =>', round(15, -1))
print('25 =>', round(25, -1))
print('35 =>', round(35, -1))
print('45 =>', round(45, -1))
# 5 => 0
# 15 => 20
# 25 => 20
# 35 => 40
# 45 => 40
偶数への丸めの定義は以下の通り。
「偶数への丸め」(round to even)は、端数が0.5より小さいなら「切り捨て」、端数が0.5より大きいならば「切り上げ」、端数がちょうど0.5なら「切り捨て」と「切り上げ」のうち結果が偶数となる方へ丸める(つまり偶数+0.5なら「切り捨て」、奇数+0.5ならば「切り上げ」となる)。 端数処理 - 偶数への丸め(round to even) - Wikipedia
round()
をサポートする組み込み型では、値は10
のマイナスndigits
乗の倍数の中で最も近いものに丸められます; 二つの倍数が同じだけ近いなら、偶数を選ぶ方に (そのため、例えばround(0.5)
とround(-0.5)
は両方とも0
に、round(1.5)
は2
に) 丸められます。 組み込み関数 - round() — Python 3.12.0 ドキュメント
偶数へ丸められるのは端数がちょうど0.5
のときなので、例えば2.5
は2
に丸められるが2.51
は3
に丸められる。
print('2.49 =>', round(2.49))
print('2.50 =>', round(2.5))
print('2.51 =>', round(2.51))
# 2.49 => 2
# 2.50 => 2
# 2.51 => 3
小数点以下1桁以降に丸めると、偶数への丸めの定義に当てはまらない場合もある。
print('0.05 =>', round(0.05, 1))
print('0.15 =>', round(0.15, 1))
print('0.25 =>', round(0.25, 1))
print('0.35 =>', round(0.35, 1))
print('0.45 =>', round(0.45, 1))
# 0.05 => 0.1
# 0.15 => 0.1
# 0.25 => 0.2
# 0.35 => 0.3
# 0.45 => 0.5
これは公式ドキュメントにもあるように、小数を浮動小数点数で正確に表せないことが原因。
注釈: 浮動小数点数に対する
round()
の振る舞いは意外なものかもしれません: 例えば、round(2.675, 2)
は予想通りの2.68
ではなく2.67
を与えます。これはバグではありません: これはほとんどの小数が浮動小数点数で正確に表せないことの結果です。
組み込み関数 - round() — Python 3.12.0 ドキュメント
例えば、表示桁数を増やすと浮動小数点数float
の0.15
は実際には0.14999....
であることが分かる。端数が0.05
より小さいため0.1
に丸められる。
print(f'{0.15:.20}')
# 0.14999999999999999445
正確に小数を表したい場合や一般的な四捨五入を実現したい場合は、次に紹介する標準ライブラリdecimal
を使う。
標準ライブラリdecimalのquantize()
標準ライブラリのdecimalモジュールを使うと正確な十進浮動小数点数を扱うことができる。
標準ライブラリなので追加のインストールは不要だが、インポートは必要。以降のサンプルコードでは、以下のようにインポートしている。他の丸めモードを使いたい場合は明示的にインポートする必要があるので注意。
from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_EVEN
Decimalオブジェクトの生成
Decimal()
でDecimal
オブジェクトを生成できる。
引数に浮動小数点数float
を指定すると、実際の値のDecimal
が生成される。
print(Decimal(0.05))
# 0.05000000000000000277555756156289135105907917022705078125
print(type(Decimal(0.05)))
# <class 'decimal.Decimal'>
このように浮動小数点数の0.05
は誤差を含む。組み込み関数round()
で0.05
などの値が想定と異なる値に丸められたのはこれが原因。
0.5
(1/2
、2の-1乗)や0.25
(1/4
、2の-2乗)は2進数で正確に表現できる。
print(Decimal(0.5))
# 0.5
print(Decimal(0.25))
# 0.25
文字列str
を指定すると正確にその値のDecimal
型として扱われる。float
をstr
に変換するにはstr()
を使う。
print(Decimal('0.05'))
# 0.05
print(Decimal(str(0.05)))
# 0.05
小数を任意の桁数で四捨五入
Decimal
のquantize()
メソッドを使うと、丸めモードを指定して数値を丸められる。
第一引数に求めたい桁数と同じ桁数のDecimal
を指定する。Decimal()
に文字列で'0.1'
や'0.01'
などを指定すればよい。整数部分を丸めたい場合は'1E1'
のように指数表記を使う。詳細は後述。
f = 123.456
print(Decimal(str(f)).quantize(Decimal('0'), ROUND_HALF_UP))
print(Decimal(str(f)).quantize(Decimal('0.1'), ROUND_HALF_UP))
print(Decimal(str(f)).quantize(Decimal('0.01'), ROUND_HALF_UP))
# 123
# 123.5
# 123.46
第二引数rounding
に丸めモードを指定する。ROUND_HALF_UP
は一般的な四捨五入。偶数への丸めである組み込み関数round()
と異なり、0.5
が1
に丸められる。
print('0.4 =>', Decimal(str(0.4)).quantize(Decimal('0'), ROUND_HALF_UP))
print('0.5 =>', Decimal(str(0.5)).quantize(Decimal('0'), ROUND_HALF_UP))
print('0.6 =>', Decimal(str(0.6)).quantize(Decimal('0'), ROUND_HALF_UP))
# 0.4 => 0
# 0.5 => 1
# 0.6 => 1
ROUND_HALF_EVEN
は組み込み関数round()
と同じ偶数への丸め。Decimal()
の引数に文字列str
で指定すると正確にその値として扱われるので、float
の誤差の影響を受けるround()
とは異なり、想定通りの結果となる。
print('0.05 =>', Decimal(str(0.05)).quantize(Decimal('0.1'), ROUND_HALF_EVEN))
print('0.15 =>', Decimal(str(0.15)).quantize(Decimal('0.1'), ROUND_HALF_EVEN))
print('0.25 =>', Decimal(str(0.25)).quantize(Decimal('0.1'), ROUND_HALF_EVEN))
print('0.35 =>', Decimal(str(0.35)).quantize(Decimal('0.1'), ROUND_HALF_EVEN))
print('0.45 =>', Decimal(str(0.45)).quantize(Decimal('0.1'), ROUND_HALF_EVEN))
# 0.05 => 0.0
# 0.15 => 0.2
# 0.25 => 0.2
# 0.35 => 0.4
# 0.45 => 0.4
丸めモードの一覧は以下の公式ドキュメントを参照。ROUND_HALF_UP
とROUND_HALF_EVEN
以外にも様々な丸めモードが使用可能。
quantize()
メソッドが返すのはDecimal
。float()
でfloat
に変換できるが、当然ながら、その場合はfloat
で表現できる値になる。
d = Decimal('123.456').quantize(Decimal('0.01'), ROUND_HALF_UP)
print(d)
# 123.46
print(type(d))
# <class 'decimal.Decimal'>
f = float(d)
print(f)
# 123.46
print(type(f))
# <class 'float'>
print(Decimal(f))
# 123.4599999999999937472239253111183643341064453125
print(Decimal(str(f)))
# 123.46
整数を任意の桁数で四捨五入
quantize()
メソッドで整数の桁に丸める場合、第一引数に'10'
のように指定しても所望の結果は得られない。
i = 99518
print(Decimal(i).quantize(Decimal('10'), ROUND_HALF_UP))
# 99518
これは、quantize()
はDecimal
オブジェクトの指数exponent
に応じて丸め処理を行うため。E
を用いる指数表記の文字列(例えば'1E1'
)を使う必要がある。
指数exponent
はas_tuple()
メソッドで確認できる。
print(Decimal('10').as_tuple())
# DecimalTuple(sign=0, digits=(1, 0), exponent=0)
print(Decimal('1E1').as_tuple())
# DecimalTuple(sign=0, digits=(1,), exponent=1)
整数はint
型で正確に表現できるのでDecimal()
にそのまま整数int
で指定しても問題ない。もちろん文字列で指定してもよい。
print(Decimal(i).quantize(Decimal('1E1'), ROUND_HALF_UP))
print(Decimal(i).quantize(Decimal('1E2'), ROUND_HALF_UP))
print(Decimal(i).quantize(Decimal('1E3'), ROUND_HALF_UP))
# 9.952E+4
# 9.95E+4
# 1.00E+5
特に有効数字を考慮せず、指数表記ではなく通常の表記にしたい場合は、公式ドキュメントで紹介されている関数を使う。
def remove_exponent(d):
return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()
d = Decimal(i).quantize(Decimal('1E2'), ROUND_HALF_UP)
print(d)
# 9.95E+4
d_remove = remove_exponent(d)
print(d_remove)
# 99500
print(type(d_remove))
# <class 'decimal.Decimal'>
Decimal
を整数int
に変換するにはint()
を使う。
i = int(d)
print(i)
# 99500
print(type(i))
# <class 'int'>
新たな関数を定義
decimal
モジュールを使う方法は正確で安心だが、型変換などが面倒な場合は、新たに関数を定義して一般的な四捨五入を実現することもできる。
例えば以下のような関数を定義する。返り値は常に浮動小数点数float
。
def my_round(number, ndigits=0):
p = 10**ndigits
return (number * p * 2 + 1) // 2 / p
桁数を指定する必要が無く、常に小数点第一位を四捨五入して整数int
に変換するのであれば、もっとシンプルな形にできる。
def my_round_int(number):
return int((number * 2 + 1) // 2)
なお、業務で使う場合など正確を期す必要があるときはdecimal
を使っておいたほうが無難。
以下は参考まで。
小数を任意の桁数で四捨五入
上で定義した関数の結果の例を示す。my_round()
は常に浮動小数点数float
を返すので、整数int
に変換したい場合はint()
を使う。
f = 123.456
print(my_round(f))
# 123.0
print(int(my_round(f)))
# 123
print(my_round(f, 1))
# 123.5
print(my_round(f, 2))
# 123.46
print(my_round_int(f))
# 123
組み込み関数round()
と異なり、一般的な四捨五入のように0.5
が1
になる。
print('0.4 =>', int(my_round(0.4)))
print('0.5 =>', int(my_round(0.5)))
print('0.6 =>', int(my_round(0.6)))
# 0.4 => 0
# 0.5 => 1
# 0.6 => 1
整数を任意の桁数で四捨五入
上で定義した関数の結果の例を示す。
i = 99518
print(int(my_round(i, -1)))
# 99520
print(int(my_round(i, -2)))
# 99500
print(int(my_round(i, -3)))
# 100000
組み込み関数round()
と異なり、一般的な四捨五入のように5
が10
になる。
print('4 =>', int(my_round(4, -1)))
print('5 =>', int(my_round(5, -1)))
print('6 =>', int(my_round(6, -1)))
# 4 => 0
# 5 => 10
# 6 => 10
注意点: 負の値の場合
上で定義した関数だと、-0.5
が0
に丸められる。
print('-0.4 =>', int(my_round(-0.4)))
print('-0.5 =>', int(my_round(-0.5)))
print('-0.6 =>', int(my_round(-0.6)))
# -0.4 => 0
# -0.5 => 0
# -0.6 => -1
負の値に対する四捨五入は様々な考え方があるが、-0.5
を-1
としたい場合は、例えば以下のようにする。組み込み関数abs()
で入力値を絶対値に変換し、math.copysign()
で取得した符号を最後に掛ける。
import math
def my_round2(number, ndigits=0):
p = 10**ndigits
return (abs(number) * p * 2 + 1) // 2 / p * math.copysign(1, number)
print('-0.4 =>', int(my_round2(-0.4)))
print('-0.5 =>', int(my_round2(-0.5)))
print('-0.6 =>', int(my_round2(-0.6)))
# -0.4 => 0
# -0.5 => -1
# -0.6 => -1