『Python数値計算ノート』ではアフィリエイトプログラムを利用して商品を紹介しています。

分数(有理数)の計算

有理数型(Fraction)

除算演算子で割り算をすると誤差が生じることがあるので、データを分数の形に保存したままで計算したい場面があります。Python で分数計算を行なうためには、fractionsモジュールの Fractionクラス (有理数型) や SymPy の Rational クラス などを用います。

fractions.Fraction()

fractions.Fraction(a,b) を使って、有理数 (Fraction クラスのオブジェクト) を生成できます。引数に 2 つの整数値を与えると、第 1 引数を分子、第 2 引数を分母とした有理数型データが定義されます。分母を省略すると 1 とみなされます。

# PYTHON_FRACTION

# In[1]

# Fraction クラスをインポート
from fractions import Fraction

# Fractionインスタンスを生成
a = Fraction(1,3)

print(a)
#1/3

引数に浮動小数点数 (float) を渡して有理数 (Fraction) に変換することもできます。

# In[2]

# 0.125 を分数に変換
a = Fraction(0.125)

print(a)
# 1/8

第 1 引数と第 2 引数に互いに互いに素でない数値を渡した場合、自動的に約分されて既約分数を返します。

# In[3]

a = Fraction(30, 120)

print(a)
# 1/4

Fractionクラスの属性 Fraction.numerator もしくは Fraction.denominator から、分子と分母の値を取得することができます。

# In[4]

x = Fraction(19,33)

# 分子を取得
a = x.numerator

# 分母を取得
b = x.denominator

print(a, b)
# 19 33

もちろん、分数同士の加減乗除も可能です。

# In[5]
val = Fraction(1,2) + Fraction(1,3)  # 1/2+1/3
print(val)  # 5/6

# In[6]
val = Fraction(1,2) - Fraction(1,3)  # 1/2-1/3
print(val)  # 1/6

# In[7]
val = Fraction(1,2) * Fraction(1,3)  # (1/2)×(1/3)
print(val)  # 1/6
 
# In[8]
val = Fraction(1,2) / Fraction(1,3)  # (1/2)÷(1/3)
print(val)  # 3/2

Fractionクラスの limit_denominatorメソッドを使うと、指定した引数に最も近い分母をもつ近似分数を得ることができます。例として、mathモジュールから円周率の値をインポートして、その近似分数を求めてみます。

# In[8]

# 円周率の値をインポート
from math import pi

# 円周率の値を分数に変換
x = Fraction(pi)

# 円周率の近似分数を取得
f_pi = x.limit_denominator(1000)

print(f_pi)
# 355/113

ちなみに、この近似分数のことを「密率」とよびます。大昔の中国人が発見したそうです。密率は小数点以下 6 桁まで一致する精度がありますが、もう少し手軽に計算に使える「約率」も求めておきましょう。

# In[9]

# 円周率の値を分数に変換
x = Fraction(pi)

# 円周率の近似分数を取得
f_pi = x.limit_denominator(10)

print(f_pi)
# 22/7

分数計算の応用です。次のような級数
 \[1+\frac{1}{2}+\frac{1}{3}+\frac{1}{4}+\frac{1}{5}+\frac{1}{6}+\cdots\]
を 20 項まで計算してみます。

# In[8]

x = 0
m = 0

for ct in range(20):
    m += 1
    x += Fraction(1, m)

print(x)
# 55835135/15519504

このサンプルコードでは、m の値を 1 つずつ増やしながら Fraction を作り、それを次々と足し合わせています。
 
数学教室の 演習問題 PS-21 の検算に用いたコードを載せておきます(ちなみに PS ナンバーは確率分野です)。fractionモジュールの Fractionクラスをインポートして、次の分数の計算をします。
 \[\begin{align*}x&=\frac{1}{6\left\{1-\left(\cfrac{5}{6}\right)^3\right\}}\\[6pt]y&=\frac{5}{36\left\{1-\left(\cfrac{5}{6}\right)^3\right\}}\\[6pt]z&=1-x-y\end{align*}\]
手計算が正しければ、$x=\cfrac{36}{91},\ y=\cfrac{30}{91},\ z=\cfrac{25}{91}$ となるはずです。

# In[9]

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

x = a * Fraction(1, 1 - b ** 3)
y = a * b * Fraction(1, 1 - b ** 3)
z = Fraction(1 - x - y)

num_list = [x, y, z]

print(num_list)
# [Fraction(36, 91), Fraction(30, 91), Fraction(25, 91)]

合ってますね。安心しました。いえね、何しろもう年なので、最近はこういう単純な計算でさえ、自分の手(それとも頭?)を信じられないのです。Python があってよかった。

sympy.Rational()

SymPy をインポートすると、コンストラクタ sympy.Rational() を使って、有理数 (分数) を扱えるようになります。

sympy.Rational(p, q=None, gcd=None)

p, q に整数を渡すと、p を分子、q を分母とする分数が生成されます。

# SYMPYT_FRACTION

# In[1]

from sympy import *

# 有理数オブジェクトを生成
x = Rational(1, 3)

print(x)
# 1/3

p に浮動小数点数を渡すと分数に変換されます。
しかし、たとえば p=0.2 を指定しても、期待している値 (1/5) は得られません。

# In[2]

# 浮動小数点数0.2を分数に変換
x = Rational(.2)

print(x)
# 3602879701896397/18014398509481984

これは浮動小数点数 0.2 を有限桁の 2 進数で表せないことに起因しています。渡した値が「0.2 の近似値」ではなく、「正確な 0.2」であることを明示するときには文字列型の ".2" を渡します。

# In[3]

# 浮動小数点数0.2を分数表式に変換
x = Rational(".2")

print(x)
# 1/5

sympy.Rational(a, b) は a, b が互いに素でない場合には、自動的に約分されて既約分数を返します。

# In[4]

# 20/48
x = Rational(24, 180)

print(x)
# 2/15

sympy.fraction()

sympy.fraction() は受け取った式を (分子, 分母) のタプルで返します。

# SYMPYT_FRACTION

# In[1]

from sympy import *

# a,b,c,dを記号として定義
var ("a:d")

# 分数をタプルに変換
x = fraction((a+b)/c)

print(x)
# (a + b, c)

Rational をタプルに変換することもできます。

# In[2]

# 有理数を生成
x = Rational(5, 11)

# 有理数をタプルに変換
y = fraction(x)

print(y)
# (5, 11)

指数関数の指数部分にマイナス符号がついていれば、指数関数を分母にします。

# In[3]

# exp(-a)をタプルに変換
x = fraction(exp(-a))

print(x)
# (1, exp(a))

コメント