Pythonプログラミング(ステップ4・反復処理・連分数)

連分数の計算について考える。

1.連分数と実数の「よい」近似法

実数 $\sqrt{2} = 1.41421356\cdots$ をなるべく「良く」近似できるような分数の表式(有理数)を見つけたい。 いちばん荒い近似としては 1 が考えられるが、これでは大雑把すぎてあまり実用的とは言えない。 $\frac{141}{100} = 1.41$ くらいにすれば、かなり近い値と言えるかもしれないが、 たとえば $\frac{7}{5} = 1.4$ とした場合でも、それほど値に大きな違いは無い。 その意味では、$\frac{7}{5}$は$\frac{141}{100}$に比べて「無駄なく」$\sqrt{2}$を近似していると言えるだろう。

このように「無駄」が少なく良い近似を与えるような有理数を求める方法として、連分数展開(continued fraction expansion)が知られている。

$\sqrt{2}$を例に、実際にそれを行ってみよう。

調べたい数を (整数部) + (小数部) に分解する。$\sqrt{2}$の場合は $\sqrt{2} = 1 + (\sqrt{2}-1) = 1 + 0.41421356\cdots$である。 ここで、 $$ \begin{eqnarray} a_0 & = & 1 \\ b_0 & = & \frac{1}{0.41421356\cdots} = \frac{1}{\sqrt{2}-1} = 1 + \sqrt{2} \end{eqnarray} $$ と置くと $$ \sqrt{2} = a_0 + 1/b_0 $$ と分解できたことになる。小数部の逆数 $b_0$ を考えるところがミソである。

次に、$b_0$に対しても、同様に整数部と小数部に分け $$ b_0 = 1 + \sqrt{2} = 2 + 0.41421356\cdots $$ とすることで、 $$ b_0 = a_1 + 1/b_1 $$ とさらに分解できる。 ただし、ここで $$ \begin{eqnarray} a_1 & = & 2 \\ b_1 & = & \frac{1}{0.41421356\cdots} = \frac{1}{\sqrt{2}-1} = 1 + \sqrt{2} \end{eqnarray} $$ である。

同様の操作を繰り返すと、展開の$n$段目(ただし$n\ge 1$)では $$ \begin{eqnarray} a_n & = & 2\\ b_n & = & 1 + \sqrt{2} \end{eqnarray} $$ であることが確認できる。

以上をまとめると、$\sqrt{2}$の連分数展開として $$ \sqrt{2} = 1 + \frac{1}{ 2 + \frac{1}{ 2 + \frac{1}{ 2 + \frac{1}{\cdots} } } } $$ が得られたことになる。

連分数を数式で表現するのはなかなか厄介であるため、上記の連分数は $a_n$を並べて $$ [1 ; 2, 2, 2, 2, 2, \cdots] $$ のように表記することが多い。 同様に $\sqrt{3}$を連分数展開すると $$ [1 ; 1, 2, 1, 2, 1, 2, \cdots ] $$ であり、黄金比のそれは $$ [1 ; 1, 1, 1, 1, 1, 1, \cdots] $$ となる。

$\sqrt{2}$は無理数であることから、この展開は無限に続くが、それを$n$段目で打ち切った数を $c_n$ とすれば、 $$ c_0 = 1 $$ $$ c_1 = 1 + \frac{1}{2} = \frac{3}{2} = 1.5 $$ $$ c_2 = 1 + \frac{1}{2 + \frac{1}{2}} = \frac{7}{5} = 1.4 $$ $$ c_3 = 1 + \frac{1}{2 + \frac{1}{2 + \frac{1}{2}}} = \frac{17}{12} = 1.41666\cdots $$ といった具合に、「小さい」分数で良い近似値が得られていることがわかる。

icon-pc 練習:連分数展開

以下に、実数値を入力すると、その連分数展開($a_n$の値)を出力するPythonコードの例を示す。

具体的な値を入力して動作を確認してみなさい。

リストや関数について知識のある者は、各段階での近似値($c_n$)を出力するように改良してみなさい。

# coding: utf-8

import math

x = float(input("x="))
b = x
while True:
    a = math.floor(b)
    print(a,"+ 1/")    
    if abs(b-a) < 0.0001:
        break
    b = 1/(b-a)
icon-hint ヒント

math.floor()は、実数値の整数部分(小数部を切り捨てた値)を与える関数である。 反対に、切り上げはmath.ceil()

icon-teacher 解説: fractionsモジュール

連分数の計算などを行う際に、分数は分数として計算したくなる。そのような用途に、Pythonの標準モジュールの中にfractionsが含まれている。

詳しい機能はマニュアル等を参照してもらうこととして、連分数展開を与えると、その値を有理数で表示するコードの例の以下に示す。

# coding: utf-8

from fractions import Fraction

contfrac = [1,2,2,2,2,2]

c = Fraction(contfrac.pop())

for a in reversed(contfrac):
    c = Fraction(a) + Fraction(1)/Fraction(c)

print(c)  

上記のコードには、これから学ぶ事項(リスト)が含まれているが、計算の流れは理解できるはずだ。

contfrac.pop() はリストの最後の要素を取り出す(同時に削除する)操作、 for a in reversed(contfrac):は、リストの最後から順に要素を取り出してcにセットする操作を表す。