Q1金額計算で 1 円のズレも許したくないときに最も適しているのはどれですか?
decimal と fractions — 誤差を避ける正確な数値計算
floatの0.1 + 0.2誤差、Decimalでの正確な金額計算と文字列から作る理由、Fractionでの約分された分数演算、4つの数値型の使い分けを実例で学べます。
float では困る場面を扱う 2 モジュールを整理します。decimal.Decimalは10 進数ベースで正確な計算を行うので金額計算で必須、fractions.Fractionは分子と分母を整数のまま保持して約分された分数のまま計算できます。どちらもfloatでは避けられない誤差を排除するための型です。
float の誤差と decimal.Decimal — 精密な計算で float を使わない
Python のfloatは2 進数で内部表現されているため、0.1のような2 進では割り切れない 10 進小数はわずかな誤差を含みます。最も有名な例が0.1 + 0.2 == 0.3 が False になるという挙動です。グラフィックや科学計算ではこの誤差は無視できますが、金額の足し算では少しのズレが致命的になります。
そのために用意されているのがdecimal.Decimalです。10 進数で内部表現されているので、文字列"0.1"から作れば誤差なしで計算できます。
0.1などで丸め誤差が出る。Decimalは内部で 10 進数表現を使うので入力した文字列のとおりの精度で計算できるが、float より遅い。金額・税率の処理では Decimal を選ぶ。from decimal import Decimal
# float の誤差
print(0.1 + 0.2) # 0.30000000000000004
print(0.1 + 0.2 == 0.3) # False ← 直感に反する!
# Decimal は文字列から作る (float から作ると誤差を受け継ぐ)
a = Decimal("0.1")
b = Decimal("0.2")
print(a + b) # 0.3
print(a + b == Decimal("0.3")) # True
# 金額計算の例
price = Decimal("1980")
tax_rate = Decimal("0.10")
total = price * (Decimal("1") + tax_rate)
print(total) # 2178.00
Decimal は float ではなく文字列から作る
Decimal(0.1)のようにfloat を渡してしまうと、すでに誤差を含んだ float が変換されるのでDecimal なのに誤差付きという事故になります。必ず Decimal("0.1") のように文字列で渡すことを習慣にしてください。テストで気付きにくく、本番で初めて 1 円ズレる典型的な落とし穴です。
fractions.Fraction — 分数を分数のまま扱う
Fractionは分子と分母を整数のまま保持して計算する型です。Fraction(1, 3)のように分子・分母で生成すると、その後の足し算・引き算・掛け算は約分された分数の形で結果が返ります。1/3を float で扱うと0.3333333333333333という近似値ですが、Fraction なら「3 分の 1」のまま扱えます。
+ / - / *の結果も Fraction で、自動で約分される。float(f)で float に変換可能だが、誤差が入る点は注意。| 型 | 内部表現 | 向いている用途 |
|---|---|---|
| int | 整数 | 個数・回数 (誤差ゼロ) |
| float | 2 進数の浮動小数 | 科学計算・グラフィック (高速、誤差あり) |
| Decimal | 10 進数表現 | 金額・税率 (10 進精度が要る場面) |
| Fraction | 分子と分母 (整数) | 比率・確率 (約分された形が欲しい場面) |
Fraction オブジェクトには分子と分母を取り出す属性が用意されています。f.numeratorで分子、f.denominatorで分母にアクセスでき、約分された後の値が返ります。たとえばFraction(2, 6)は内部でFraction(1, 3)に正規化されているので、.numeratorは1、.denominatorは3です。
| 属性 / メソッド | 意味 | 例 |
|---|---|---|
| f.numerator | 分子 (英: numerator) | Fraction(1, 3).numerator → 1 |
| f.denominator | 分母 (英: denominator) | Fraction(1, 3).denominator → 3 |
| float(f) | float に変換 | float(Fraction(1, 2)) → 0.5 |
理解度チェック
まずは1問ずつ答えてみましょう。
Q2Decimalを作るとき誤差が乗らない正しい書き方はどれですか?
Q3比率や確率を約分された分数のまま計算したいときに使うのはどれですか?