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

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

Python の decimal / fractions モジュールを基礎から解説します。float の 0.1 + 0.2 != 0.3 問題、Decimal による正確な金額計算、Decimal は float ではなく文字列から作る理由、Fraction による約分された分数演算まで、ハンズオンで学べます。

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比率や確率を 約分された分数のまま 計算したいときに使うのはどれですか?