分数で正確に計算する(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) # 21/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.1415929203539825355/113 は「密率」と呼ばれる円周率の近似分数で、中国の数学者・祖沖之が 5 世紀に発見した。分母が小さい割に驚くほど精度が高い。
分子と分母の取得
numerator と denominator 属性で分子・分母を取り出せる。
from fractions import Fraction
f = Fraction(7, 12)
print(f.numerator) # 7
print(f.denominator) # 12使いどころ
Fraction は金融計算や数学的な厳密性が求められる場面で有用だ。ただし、計算速度は浮動小数点数より遅く、分子・分母が大きくなるとメモリ消費も増える。パフォーマンスが重要な場面では decimal.Decimal や通常の float を検討する。
分数のまま正確に計算できる。約分も自動。ただし速度は遅め。
高速だが、10 進数を正確に表現できない場合がある。科学計算向き。
有理数の範囲で正確な計算が必要なら Fraction を選ぶとよい。



