√2を高精度で計算する

昨日はcos20°を求めましたが、今日は√2を求めます。100万桁を目標にしましょう。
nの循環連分数になります。√2は、

√2 - 1 = 1 / (2 + 1 / (2 + ...)) = [ 0; 2, 2, ... ]

となります。連分数を途中で打ち切ったときの分数をan / bnと置くと、

an+2 = 2an+1 + an
bn+2 = 2bn+1 + bn
a0 = 1, a1 = 0, b0 = 0, b1 = 1

さて、漸化式は次のように式を加えることで行列に変えることができます。

an+2 = 2an+1 + an
an+1 = an+1

 A = \left(\begin{array}2&1\\1&0\end{array}\right)
 u_n = \left(\begin{array}a_{n+1}\\a_n\end{array}\right)

とおくと、

u1 = Au0

行列になると結合則が成り立つから分割統治法が使えますが、すべて同じ係数なので単にべき乗で表すことができますね。

un = Anu0

さて、100万桁の精度を出すにはどれだけのべき乗を求めなければならないでしょうか。
Aの固有値を求めると、最大の固有値は1 + √2となります。ゆえに、Anの各項は(1 + √2)n程度となります。また、an / bnが√2 - 1の近似になるのですが、(an + bn)2 - bn2 = ±1となるので、

(an + bn) / bn = √2(1 - 1 / 2bn2)

で精度が1 / 2bn2程度となります。E乗だとするとより正確には、

4 / (1 + √2)2E = 1 / 10N

ここから何乗すればいいかわかります。
100万桁を求めるのに82秒かかりました。もうちょっと速いイメージだったのですが。これを10進表示しようとするとさらに10倍とかかかりそうです。

from math import sqrt, log
import time

def square(A):
    b = A[0][1] * A[0][1]
    a11 = A[0][0] * A[0][0] + b
    a12 =  A[0][1] * (A[0][0] + A[1][1])
    a22 = A[1][1] * A[1][1] + b
    return [ [ a11, a12 ], [ a12, a22 ] ]

def mul_mat(A, B):
    return [ [ sum(A[i][k] * B[k][j] for k in xrange(2))
                for j in xrange(2) ] for i in xrange(2) ]

def pow_mat(A, e):
    if e == 1:
        return A
    else:
        B = pow_mat(A, e / 2)
        if e % 2 != 0:
            return mul_mat(square(B), A)
        else:
            return square(B)

t0 = time.clock()
N = 10 ** 6
E = int((log(4) + N * log(10) / (2 * log(1 + sqrt(2))))) + 1
A = [ [ 2, 1 ], [ 1, 0 ] ]
B = pow_mat(A, E)
(B[0][1] + B[0][0]) * 10 ** N / B[0][0]
print time.clock() - t0

さて、昨日の問題も同様に速く、と考えたくなりますが、しかし、平方根のようにうまく連分数で表すこともできず、またいつものニュートン法でもあまりうまく分数で近似をすることができないようなのです。