高精度な小数計算(decimal.Decimal)|Python

Python の float は 2 進浮動小数点数であり、0.1 のような 10 進小数を正確に表現できない。金融計算や会計処理など、10 進数での正確な計算が必要な場面では decimal.Decimal を使う。

Decimal の基本

Decimal は 10 進数を正確に表現できる。

from decimal import Decimal

# float の場合
print(0.1 + 0.1 + 0.1)  # 0.30000000000000004

# Decimal の場合
a = Decimal('0.1')
print(a + a + a)  # 0.3

Decimal は文字列から作成するのが基本だ。Decimal(0.1) のように浮動小数点数から作ると、元の誤差がそのまま持ち込まれる。

from decimal import Decimal

# 文字列から(正確)
Decimal('0.1')  # Decimal('0.1')

# float から(誤差を含む)
Decimal(0.1)  # Decimal('0.1000000000000000055511151231257827021181583404541015625')

四則演算

Decimal 同士の演算は Decimal を返す。

from decimal import Decimal

price = Decimal('1980')
tax_rate = Decimal('0.10')
tax = price * tax_rate

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

floatDecimal を直接演算することはできない。型を揃える必要がある。

from decimal import Decimal

a = Decimal('1.5')
b = 2.0

# a + b  # TypeError: unsupported operand type(s)
a + Decimal(str(b))  # Decimal('3.5')

精度の設定

getcontext() で演算の精度(有効桁数)を設定できる。デフォルトは 28 桁。

from decimal import Decimal, getcontext

getcontext().prec = 50  # 50 桁に設定

result = Decimal(1) / Decimal(7)
print(result)
# 0.14285714285714285714285714285714285714285714285714

精度を上げると計算は遅くなるが、必要な桁数を確保できる。

丸めモード

Decimal は丸めの方法を細かく制御できる。金融計算では「銀行丸め(ROUND_HALF_EVEN)」がよく使われる。

from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_EVEN

value = Decimal('2.5')

# 四捨五入(一般的な方法)
print(value.quantize(Decimal('1'), rounding=ROUND_HALF_UP))  # 3

# 銀行丸め(最近接偶数への丸め)
print(value.quantize(Decimal('1'), rounding=ROUND_HALF_EVEN))  # 2

quantize メソッドで小数点以下の桁数と丸めモードを指定する。

ROUND_HALF_UP

一般的な四捨五入。0.5 は切り上げる。

ROUND_HALF_EVEN

銀行丸め。0.5 のとき最近接の偶数に丸める。統計的な偏りが少ない。

消費税計算の例

実際の金額計算では、端数処理のタイミングと方法が重要になる。

from decimal import Decimal, ROUND_DOWN

def calc_tax(price, tax_rate=Decimal('0.10')):
    """税込価格を計算(税額は切り捨て)"""
    tax = (price * tax_rate).quantize(Decimal('1'), rounding=ROUND_DOWN)
    return price + tax

items = [Decimal('198'), Decimal('350'), Decimal('1280')]

for price in items:
    total = calc_tax(price)
    print(f'{price}円 → {total}円(税込)')

消費税の端数処理は法的に決まっていないが、多くの場合は切り捨てが採用される。

float との比較

float

高速で、科学計算や機械学習に適している。2 進数ベースのため 10 進小数に誤差が出る。

Decimal

10 進数を正確に扱える。金融・会計向き。速度は float より遅い。

使いどころ

Decimal は以下のような場面で活躍する。

金額計算(税込価格、割引、合計など)
会計・経理システム
為替レート計算
利息計算

一方、科学計算や機械学習など、大量の数値を高速に処理する必要がある場面では float や NumPy を使う方が適切だ。計算の性質に応じて使い分けることが重要である。