分数

このページではPythonで分数を扱う方法について解説します。

fractionsモジュール

fractionsモジュールとFractionの生成

Pythonの標準ライブラリにはfractionsという分数を扱うモジュールが用意されています。

https://docs.python.org/ja/3/library/fractions.html

このモジュールのFraction型で分数を表現することが可能で、インスタンス生成時に引数で分子、分母をしていします。

Fractionの生成
Fraction(分子, 分母)

例えば1/2と2/3を扱いたい場合、以下のようにFractionを生成します。

from fractions import Fraction

# 1/2
frac1 = Fraction(1, 2)
# 2/3
frac2 = Fraction(2, 3)

print(frac1, frac2) # 1/2 2/3

なお、約分はインスタンス生成時に自動で実行されます。

my_frac = Fraction(2, 4)
print(my_frac) # 1/2

分数の計算

生成したFraction型は演算が定義されているためそのまま計算できます。以下のコードは先程生成した分数に対して四則演算を行っています。

# 上のコードの続き
frac3 = frac1 + frac2
print(frac3) # 7/6

frac4 = frac1 - frac2
print(frac4) # -1/6

frac5 = frac1 * frac2
print(frac5) # 1/3

frac6 = frac1 / frac2
print(frac6) # 3/4

無論以下のようにintやfloat等とも演算可能です。

num = frac1 * 1.2
print(num) # 0.6

分子と分母を取得

分子、分母を取得するためにプロパティが用意されています。

  • numerator:分子
  • denominator:分母

またPython3.8以降ではas_integer_ratioメソッドを使用して分子、分母のタプルを得ることができます。

from fractions import Fraction

my_frac = Fraction(3, 7)
print(my_frac.numerator, my_frac.denominator) # 3 7
print(my_frac.as_integer_ratio()) # (3, 7)

小数を分数に変換

Fractionの引数に小数を指定すると近い値の分数に変換されます。

from math import pi
pfrac = Fraction(pi) # Fraction(884279719003555, 281474976710656)

円周率の有理数による近似値として紀元前3世紀にアルキメデスが発見した 22/7がよく知られています。こういった簡単な分数に直す方法としてlimit_denominatorメソッドを使用すると引数で指定した最大分母サイズで近似値を得ることが可能です。

limit_denominator(*max_denominator=1000000*)

円周率について最大分母を50以下で近似すると、アルキメデスの22/7が得られることが確認できます。

from fractions import Fraction

pfrac2 = pfrac.limit_denominator(50)
print(pfrac2) # Fraction(22, 7)

floatへの変換

また、逆にfloatの引数に指定するとfloat型を得ることが可能です。

float(pfrac2) # 3.142857142857143

NumPy配列の要素をFractionに変換

引数を指定していない場合、0/1、つまり0と同等のFractionオブジェクトが取得できます。これと演算することによりNumPyの要素をFractionに変換する方法があります。

以下のコードでは、行列にスカラーで割り算をしていますが要素を一旦0Fractionとの演算により変換し誤差を抑えています。

from fractions import Fraction
import numpy as np

z = Fraction()
A = np.array([[1,  2],[3, 4]])
A2 = A + z
# array([[Fraction(1, 1), Fraction(2, 1)],
#        [Fraction(3, 1), Fraction(4, 1)]], dtype=object)

D = A2 / 2
print(D)
# array([[Fraction(1, 2), Fraction(1, 1)],
#       [Fraction(3, 2), Fraction(2, 1)]], dtype=object)

補足1 処理の遅さ

有理数のみ扱う場合は誤差が一番なく簡単に扱える便利なFractionですが、当然ながら保有している情報量が多いためfloatと比較して遅いと言われています。10000回ランダムに生成し掛け算した場合の計測コードを補足として掲載します。

import time
import random
from fractions import Fraction

t1 = time.time()
for i in range(100000):
    float1 = random.randint(1, 100) / random.randint(1, 100)
    float2 = random.randint(1, 100) / random.randint(1, 100)
    float3 = float1 * float2

elapsed_time1 = time.time() - t1
print(f'float elapsed_time:{elapsed_time1}')

t2 = time.time()
for i in range(100000):
    frac1 = Fraction(random.randint(1, 100), random.randint(1, 100))
    frac2 = Fraction(random.randint(1, 100), random.randint(1, 100))
    frac3 = frac1 * frac2

elapsed_time2 = time.time() - t2
print(f'frac elapsed_time:{elapsed_time2}')

私の手元の環境では以下の違いがありました。
実行結果

float elapsed_time:0.21874618530273438
frac elapsed_time:0.5316271781921387

処理時間を計測する

2021年8月31日

補足2 SymPy

標準ライブラリ以外にSymPyという数式演算のライブラリがあります。代数方程式を解いたり微積計算をすることができる高機能なライブラリで、分数を扱うことも可能なので補足として簡単に紹介します。

https://www.sympy.org/en/index.html

以下のコマンドでインストールすることができます

pip install sympy

Rationalで分数を扱うことができます。

from sympy import Rational
my_frac = Rational(1, 3)
print(my_frac)

前述の通り様々な機能がありますが、分数においては標準ライブラリのFractionよりリストなどの要素の場合でも文字列表現が簡潔で見やすいというメリットがあります。

print([my_frac]) # 1/3
# Fractionの場合は[Fraction(1, 3)]となる