順番に読み進めながら学べます

decimal と fractions — 誤差を避ける正確な数値計算

floatの0.1 + 0.2誤差、Decimalでの正確な金額計算と文字列から作る理由、Fractionでの約分された分数演算、4つの数値型の使い分けを実例で学べます。

float では困る場面を扱う 2 モジュールを整理します。decimal.Decimal10 進数ベースで正確な計算を行うので金額計算で必須、fractions.Fraction分子と分母を整数のまま保持して約分された分数のまま計算できます。どちらもfloatでは避けられない誤差を排除するための型です。

float の誤差と decimal.Decimal — 精密な計算で float を使わない

Python のfloat2 進数で内部表現されているため、0.1のような2 進では割り切れない 10 進小数はわずかな誤差を含みます。最も有名な例が0.1 + 0.2 == 0.3 が False になるという挙動です。グラフィックや科学計算ではこの誤差は無視できますが、金額の足し算では少しのズレが致命的になります。

そのために用意されているのがdecimal.Decimalです。10 進数で内部表現されているので、文字列"0.1"から作れば誤差なしで計算できます。

float と Decimal の違い
float0.1 + 0.2→ 0.30000000000000004誤差ありDecimalDecimal('0.1') + Decimal('0.2')→ Decimal('0.3')誤差なし
floatは CPU 命令で高速だが、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 円ズレる典型的な落とし穴です。

float の誤差を観察してから、Decimal で正確な税込み計算に切り替えます。

① decimal モジュールから Decimal クラスを読み込んでください

② float で0.1 + 0.2を計算し、float: ◯◯の形で表示してください(誤差付きの結果が出るはずです)

③ float の0.1 + 0.2 == 0.3の判定結果をfloat 等しい?: ◯◯の形で表示してください(直感に反してFalseになるはずです)

④ Decimal で0.1 + 0.2を計算し、Decimal: ◯◯の形で表示してください(誤差なしの結果が出るはずです)

⑤ 税抜き価格1980円と税率10%を Decimal で定義し、税込み金額を計算して税込み: ◯◯の形で表示してください

Python エディタ

コードを実行してください

fractions.Fraction — 分数を分数のまま扱う

Fraction分子と分母を整数のまま保持して計算する型です。Fraction(1, 3)のように分子・分母で生成すると、その後の足し算・引き算・掛け算は約分された分数の形で結果が返ります。1/3を float で扱うと0.3333333333333333という近似値ですが、Fraction なら「3 分の 1」のまま扱えます。

Fraction の特徴
Fraction(1, 3)+ Fraction(1, 6)Fraction(1, 2)(自動で約分)
Fraction(分子, 分母)で作る整数比の型。+ / - / *の結果も Fraction で、自動で約分される。float(f)で float に変換可能だが、誤差が入る点は注意。
内部表現向いている用途
int整数個数・回数 (誤差ゼロ)
float2 進数の浮動小数科学計算・グラフィック (高速、誤差あり)
Decimal10 進数表現金額・税率 (10 進精度が要る場面)
Fraction分子と分母 (整数)比率・確率 (約分された形が欲しい場面)

Fraction オブジェクトには分子と分母を取り出す属性が用意されています。f.numeratorで分子、f.denominatorで分母にアクセスでき、約分された後の値が返ります。たとえばFraction(2, 6)は内部でFraction(1, 3)に正規化されているので、.numerator1.denominator3です。

属性 / メソッド意味
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

Fraction で分数の足し算と属性アクセスを試します。1/3 + 1/6を Fraction のまま計算すると、自動で約分された結果が得られることを確認します。

① fractions モジュールから Fraction クラスを読み込んでください

1/31/6をそれぞれ Fraction で作ってください

③ 2 つの Fraction を足して、結果を和: ◯/◯の形で表示してください(自動で約分されるはずです)

④ 結果から分子分母を属性で取り出して、分子: ◯ 分母: ◯の形で表示してください

⑤ 結果を float に変換して、float に変換: ◯.◯の形で表示してください

Python エディタ

コードを実行してください
QUIZ

理解度チェック

まずは1問ずつ答えてみましょう。

Q1金額計算で 1 円のズレも許したくないときに最も適しているのはどれですか?

Q2Decimalを作るとき誤差が乗らない正しい書き方はどれですか?

Q3比率や確率を約分された分数のまま計算したいときに使うのはどれですか?