分数で正確に計算する(fractions.Fraction)|Python

浮動小数点数では 1/3 を正確に表現できない。0.333... と無限に続く小数を有限のビットで表すため、どうしても誤差が生じる。分数のまま計算したい場面では fractions.Fraction を使う。

Fraction の基本

Fraction は分子と分母を整数で保持し、演算結果も分数のまま返す。

from fractions import Fraction

a = Fraction(1, 3)
b = Fraction(1, 6)

print(a + b)  # 1/2
print(a * b)  # 1/18
print(a / b)  # 2

1/3 + 1/6 は通分して 2/6 + 1/6 = 3/6 = 1/2 となる。Fraction は自動的に約分してくれるため、結果は常に既約分数で得られる。

浮動小数点数との違い

浮動小数点数で同じ計算をすると、微小な誤差が紛れ込む。

# 浮動小数点数
x = 1/3 + 1/6
print(x)  # 0.5
print(x == 0.5)  # True(この例ではたまたま一致)

# しかし別の例では
y = 0.1 + 0.1 + 0.1
print(y)  # 0.30000000000000004
print(y == 0.3)  # False

一方、Fraction なら誤差は生じない。

from fractions import Fraction

y = Fraction(1, 10) + Fraction(1, 10) + Fraction(1, 10)
print(y)  # 3/10
print(y == Fraction(3, 10))  # True

様々な初期化方法

Fraction は複数の方法でインスタンス化できる。

from fractions import Fraction

# 分子と分母を指定
Fraction(3, 4)  # 3/4

# 整数から
Fraction(5)  # 5

# 文字列から
Fraction('3/4')  # 3/4
Fraction('0.75')  # 3/4
Fraction('-1.25')  # -5/4

# 浮動小数点数から(注意が必要)
Fraction(0.5)  # 1/2
Fraction(0.1)  # 3602879701896397/36028797018963968(誤差を含む)

浮動小数点数から Fraction を作ると、元の浮動小数点数が持っていた誤差がそのまま分数に変換される。0.1 は内部的に 0.1 ちょうどではないため、奇妙な分数になる。文字列 '0.1' から作れば Fraction(1, 10) が得られる。

limit_denominator で近似

巨大な分母を持つ分数を、指定した分母以下の近似分数に変換できる。

from fractions import Fraction
import math

# 円周率を分数で近似
pi_frac = Fraction(math.pi)
print(pi_frac)  # 884279719003555/281474976710656

# 分母を 1000 以下に制限
approx = pi_frac.limit_denominator(1000)
print(approx)  # 355/113
print(float(approx))  # 3.1415929203539825

355/113 は「密率」と呼ばれる円周率の近似分数で、中国の数学者・祖沖之が 5 世紀に発見した。分母が小さい割に驚くほど精度が高い。

分子と分母の取得

numeratordenominator 属性で分子・分母を取り出せる。

from fractions import Fraction

f = Fraction(7, 12)
print(f.numerator)  # 7
print(f.denominator)  # 12

使いどころ

Fraction は金融計算や数学的な厳密性が求められる場面で有用だ。ただし、計算速度は浮動小数点数より遅く、分子・分母が大きくなるとメモリ消費も増える。パフォーマンスが重要な場面では decimal.Decimal や通常の float を検討する。

Fraction

分数のまま正確に計算できる。約分も自動。ただし速度は遅め。

float

高速だが、10 進数を正確に表現できない場合がある。科学計算向き。

有理数の範囲で正確な計算が必要なら Fraction を選ぶとよい。