Pythonで小数・整数を四捨五入するroundとDecimal.quantize

Pythonで数値(浮動小数点float型または整数int型)を四捨五入や偶数への丸めで丸める場合、以下の方法があります。

組み込み関数のround() 標準ライブラリdecimalのquantize() 新たな関数を定義

それぞれについて、小数を任意の桁数で丸める方法、整数を任意の桁数で丸める方法を示す。 なお、組み込み関数のroundは一般的な四捨五入ではなく偶数への丸めなので気をつけてください。詳細は後述します。 pandasでの数値の丸め(四捨五入、偶数への丸め)については

組み込み関数round()

組み込み関数にround()が用意されています。モジュールをインポートすることなく使えます。

  1. 組み込み関数 round() — Python 3.6.3 ドキュメント

第一引数に元の数値、第二引数に桁数(何桁に丸めるか)を指定します。 小数を任意の桁数で丸める 浮動小数点float型に対する処理の例を示す。 第二引数を省略すると整数に丸められます。型も整数int型になります。

f = 123.456

print(round(f))
# 123

print(type(round(f)))
# <class 'int'>

第二引数を指定すると浮動小数点float型を返します。 正の整数を指定すると小数点以下の桁、負の整数を指定すると整数の桁(位)の指定となります。-1は10の位に丸め、-2は100の位に丸める。0は整数(1の位)に丸められるが省略した場合と異なり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型に対する処理の例を示す。 第二引数を省略した場合や0、正の整数を指定した場合は元の値をそのまま返します。負の整数を指定すると対応する整数の桁に丸められます。いずれの場合も整数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()は一般的な四捨五入ではなく、偶数への丸め Python3での組み込み関数round()による丸めは一般的な四捨五入ではなく、偶数への丸め(銀行家の丸め)になるので気をつけてください。

端数処理 - Wikipedia

公式ドキュメントに書いてあるように、0.5が0に丸められたり、5が0に丸められたりします。

  1. 組み込み関数 round() — Python 3.6.3 ドキュメント
print('0.4 =>', round(0.4))
print('0.5 =>', round(0.5))
print('0.6 =>', round(0.6))
# 0.4 => 0
# 0.5 => 0
# 0.6 => 1

print('4 =>', round(4, -1))
print('5 =>', round(5, -1))
print('6 =>', round(6, -1))
# 4 => 0
# 5 => 0
# 6 => 10

偶数への丸めの定義は以下の通り。

端数が0.5より小さいなら切り捨て、端数が0.5より大きいならは切り上げ、端数がちょうど0.5なら切り捨てと切り上げのうち結果が偶数となる方へ丸める。 端数処理 - Wikipedia

0.5が常に切り捨てられるわけではない。

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

小数点以下2桁以降の処理では偶数への丸めの定義にも当てはまらない場合もあります。

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 を与えます。これはバグではありません: これはほとんどの小数が浮動小数点数で正確に表せないことの結果です。 2. 組み込み関数 round() — Python 3.6.3 ドキュメント

一般的な四捨五入や小数に対して正確な偶数への丸めを実現したい場合は、後述の標準ライブラリdecimalのquantizeか新たな関数を定義する方法を使います。 また、Python2のround()は偶数への丸めではなく四捨五入なので気をつけてください。

標準ライブラリdecimalのquantize()

標準ライブラリのdecimalモジュールを使うと正確な十進浮動小数点数を扱うことができます。

9.4. decimal — 十進固定及び浮動小数点数の算術演算 — Python 3.6.5 ドキュメント

decimalモジュールのquantize()メソッドを使うと、丸めモードを指定して数値を丸めることができます。

9.4. decimal quantize() — Python 3.6.3 ドキュメント 9.4. decimal 丸めモード — Python 3.6.3 ドキュメント

quantize()メソッドの引数roundingにROUND_HALF_UPを指定すると一般的な四捨五入、ROUND_HALF_EVENを指定すると偶数への丸めとなります。 decimalモジュールは標準ライブラリなので追加のインストールは不要だが、インポートは必要。

from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_EVEN

Decimalオブジェクトの生成

Decimal()でDecimal型のオブジェクトを生成できます。 引数にfloat型を指定すると、実際にどのような値として扱われているかが分かる。

print(Decimal(0.05))
# 0.05000000000000000277555756156289135105907917022705078125

print(type(Decimal(0.05)))
# <class 'decimal.Decimal'>

例のように0.05は正確に0.05とは扱われていません。上述の組み込み関数round()で例の0.05を含む小数の値で想定とは異なる値に丸められたのはこれが原因。 0.5は1/2(2の-1乗)なので2進法で正確に表現できます。 print(Decimal(0.5))

0.5

float型ではなく文字列str型を指定すると正確にその値のDecimal型として扱われる。

print(Decimal('0.05'))
# 0.05

小数を任意の桁数で四捨五入・偶数への丸め

Decimal型のオブジェクトからquantize()を呼び出し、値を丸める。 quantize()の第一引数に求めたい桁数と同じ桁数の数値を'0.1'や'0.01'のように文字列で指定します。 さらに引数roundingに丸めモードを指定します。ROUND_HALF_UPを指定すると一般的な四捨五入となります。

f = 123.456

print(Decimal(str(f)).quantize(Decimal('0'), rounding=ROUND_HALF_UP))
# 123

print(Decimal(str(f)).quantize(Decimal('0.1'), rounding=ROUND_HALF_UP))
# 123.5

print(Decimal(str(f)).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP))
# 123.46

組み込み関数round()と異なり、0.5が1に丸められます。

print('0.4 =>', Decimal(str(0.4)).quantize(Decimal('0'), rounding=ROUND_HALF_UP))
print('0.5 =>', Decimal(str(0.5)).quantize(Decimal('0'), rounding=ROUND_HALF_UP))
print('0.6 =>', Decimal(str(0.6)).quantize(Decimal('0'), rounding=ROUND_HALF_UP))
# 0.4 => 0
# 0.5 => 1
# 0.6 => 1

引数roundingにROUND_HALF_EVENを指定すると組み込み関数round()と同様に偶数への丸めとなります。 上述のように、Decimal()の引数に浮動小数点float型で指定するとfloat型の実際の値と等しい値のDecimalオブジェクトとして扱われるので、quantize()メソッドを使っても組み込み関数round()と同様に、想定とは異なる結果となります。

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

print('0.05 =>', Decimal(0.05).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.15 =>', Decimal(0.15).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.25 =>', Decimal(0.25).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.35 =>', Decimal(0.35).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.45 =>', Decimal(0.45).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
# 0.05 => 0.1
# 0.15 => 0.1
# 0.25 => 0.2
# 0.35 => 0.3
# 0.45 => 0.5

Decimal()の引数に文字列str型で指定すると正確にその値のDecimalオブジェクトとして扱われるので、想定通りの結果となります。

print('0.05 =>', Decimal(str(0.05)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.15 =>', Decimal(str(0.15)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.25 =>', Decimal(str(0.25)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.35 =>', Decimal(str(0.35)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.45 =>', Decimal(str(0.45)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
# 0.05 => 0.0
# 0.15 => 0.2
# 0.25 => 0.2
# 0.35 => 0.4
# 0.45 => 0.4

0.5はfloat型でも正確に扱えるので、整数に丸める場合はDecimal()の引数にfloat型で指定しても特に問題はないが、小数点以下の桁数に丸める場合は文字列str型で指定しておいたほうが無難。 例えば2.675はfloat型では実際は2.67499....であるため、小数点以下2桁に丸める場合はDecimal()に文字列で指定しないと、四捨五入(ROUND_HALF_UP)でも偶数への丸め(ROUND_HALF_EVEN)でも想定と異なる結果となります。

print(Decimal(2.675))
# 2.67499999999999982236431605997495353221893310546875

print(Decimal(2.675).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP))
# 2.67

print(Decimal(str(2.675)).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP))
# 2.68

print(Decimal(2.675).quantize(Decimal('0.01'), rounding=ROUND_HALF_EVEN))
# 2.67

print(Decimal(str(2.675)).quantize(Decimal('0.01'), rounding=ROUND_HALF_EVEN))
# 2.68

なお、quantize()メソッドが返すのはDecimal型。float型の数値と演算したりするときはfloat()でfloat型に変換しないとエラーになるので気をつけてください。

d = Decimal('123.456').quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)

print(d)
# 123.46

print(type(d))
# <class 'decimal.Decimal'>

# print(1.2 + d)
# TypeError: unsupported operand type(s) for +: 'float' and 'decimal.Decimal'

print(1.2 + float(d))
# 124.66

整数を任意の桁数で四捨五入・偶数への丸め

整数の桁に丸めたい場合、第一引数に'10'のように指定しても所望の結果は得られない。

i = 99518

print(Decimal(i).quantize(Decimal('10'), rounding=ROUND_HALF_UP))
# 99518

これは、quantize()ではDecimalオブジェクトの指数exponentに応じて丸め処理を行っているが、Decimal('10')は指数が1ではなく0であることが原因。 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)

そのままだと結果もEを使った指数表記になるので、通常の表記にしたい、あるいは、丸めたあとで整数int型と演算したい、といった場合は、int()で変換します。

print(Decimal(i).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP))
# 9.952E+4

print(int(Decimal(i).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP)))
# 99520

print(int(Decimal(i).quantize(Decimal('1E2'), rounding=ROUND_HALF_UP)))
# 99500

print(int(Decimal(i).quantize(Decimal('1E3'), rounding=ROUND_HALF_UP)))
# 100000

引数roundingにROUND_HALF_UPを指定すると一般的な四捨五入となり、例えば5が10に丸められます。

print('4 =>', int(Decimal(4).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP)))
print('5 =>', int(Decimal(5).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP)))
print('6 =>', int(Decimal(6).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP)))
# 4 => 0
# 5 => 10
# 6 => 10

なお、整数はint型で正確に表現できているのでDecimal()にそのまま整数int型で指定して問題ありません。もちろん文字列で指定しても問題ない。

新たな関数を定義

decimalモジュールを使う方法は正確で安心だが、型変換などもろもろが面倒な場合は、新たに関数を定義して一般的な四捨五入を実現することもできます。 いろんなやり方が考えられるが、例えば以下のような関数。

def my_round(val, digit=0):
    p = 10 ** digit
    return (val * p * 2 + 1) // 2 / p

桁数を指定する必要が無く、常に小数点第一位を四捨五入するのであれば、もっとシンプルな形にもできます。

my_round_int = lambda x: int((x * 2 + 1) // 2)

なお、業務で使う場合など正確を期す必要があるときはdecimalを使っておいたほうが無難。 以下は参考まで。 小数を任意の桁数で四捨五入

print(int(my_round(f)))
# 123

print(my_round_int(f))
# 123

print(my_round(f, 1))
# 123.5

print(my_round(f, 2))
# 123.46

roundと違い、一般的な四捨五入の通り0.5が1.0になります。

print(int(my_round(0.4)))
print(int(my_round(0.5)))
print(int(my_round(0.6)))
# 0
# 1
# 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(int(my_round(4, -1)))
print(int(my_round(5, -1)))
print(int(my_round(6, -1)))
# 0
# 10
# 10

注意点: 負の値の場合 上の例の関数だと、-0.5が0に丸められます。

print(int(my_round(-0.4)))
print(int(my_round(-0.5)))
print(int(my_round(-0.6)))
# 0
# 0
# -1

負の値に対する四捨五入は様々な考え方があるが、-0.5を-1としたい場合は、例えば以下のように修正できます。

import math

def my_round2(val, digit=0):
    p = 10 ** digit
    s = math.copysign(1, val)
    return (s * val * p * 2 + 1) // 2 / p * s

print(int(my_round2(-0.4)))
print(int(my_round2(-0.5)))
print(int(my_round2(-0.6)))
# 0
# -1
# -1
Last Updated: 6/26/2019, 10:34:03 PM