Python 2、110バイト
n=input()
x=p=7*n|1
while~-p:x=p/2*x/p+2*10**n;p-=2
l=m=0
for c in`x`:
l=l*(p==c)+1;p=c
if l>m:m=l;print p*l
チェックする最大桁数は、stdinから取得されます。PyPy 5.3では、10,000桁が約2秒で終わります。
サンプルの使用法
$ echo 10000 | pypy pi-runs.py
3
33
111
9999
99999
999999
役に立つもの
from sys import argv
from gmpy2 import mpz
def pibs(a, b):
if a == b:
if a == 0:
return (1, 1, 1123)
p = a*(a*(32*a-48)+22)-3
q = a*a*a*24893568
t = 21460*a+1123
return (p, -q, p*t)
m = (a+b) >> 1
p1, q1, t1 = pibs(a, m)
p2, q2, t2 = pibs(m+1, b)
return (p1*p2, q1*q2, q2*t1 + p1*t2)
if __name__ == '__main__':
from sys import argv
digits = int(argv[1])
pi_terms = mpz(digits*0.16975227728583067)
p, q, t = pibs(0, pi_terms)
z = mpz(10)**digits
pi = 3528*q*z/t
l=m=0
x=0
for c in str(pi):
l=l*(p==c)+1;p=c
if l>m:m=l;print x,p*l
x+=1
このために、CudnovskyからRamanujan 39に切り替えました。Chudnovskyは1億桁でシステムのメモリを使い果たしましたが、Ramanujanはわずか38分で4億に達しました。これは、少なくともリソースが限られているシステムでは、用語の成長率が最終的に遅くなる別のケースだと思います。
サンプルの使用法
$ python pi-ramanujan39-runs.py 400000000
0 3
25 33
155 111
765 9999
766 99999
767 999999
710106 3333333
22931752 44444444
24658609 777777777
386980421 6666666666
より高速な無制限ジェネレーター
問題の説明に記載されているリファレンス実装は興味深いものです。それは、Piの桁のための論文Unbounded Spigot Algorithmsから直接取られた、無制限のジェネレーターを使用します。著者によると、提供された実装は「意図的に曖昧」であるため、意図的に難読化することなく、著者によってリストされた3つのアルゴリズムすべての新しい実装を作成することにしました。また、Ramanujan#39に基づいて4番目を追加しました。
try:
from gmpy2 import mpz
except:
mpz = long
def g1_ref():
# Leibniz/Euler, reference
q, r, t = mpz(1), mpz(0), mpz(1)
i, j = 1, 3
while True:
n = (q+r)/t
if n*t > 4*q+r-t:
yield n
q, r = 10*q, 10*(r-n*t)
q, r, t = q*i, (2*q+r)*j, t*j
i += 1; j += 2
def g1_md():
# Leibniz/Euler, multi-digit
q, r, t = mpz(1), mpz(0), mpz(1)
i, j = 1, 3
z = mpz(10)**10
while True:
n = (q+r)/t
if n*t > 4*q+r-t:
for d in digits(n, i>34 and 10 or 1): yield d
q, r = z*q, z*(r-n*t)
u, v, x = 1, 0, 1
for k in range(33):
u, v, x = u*i, (2*u+v)*j, x*j
i += 1; j += 2
q, r, t = q*u, q*v+r*x, t*x
def g2_md():
# Lambert, multi-digit
q, r, s, t = mpz(0), mpz(4), mpz(1), mpz(0)
i, j, k = 1, 1, 1
z = mpz(10)**49
while True:
n = (q+r)/(s+t)
if n == q/s:
for d in digits(n, i>65 and 49 or 1): yield d
q, r = z*(q-n*s), z*(r-n*t)
u, v, w, x = 1, 0, 0, 1
for l in range(64):
u, v, w, x = u*j+v, u*k, w*j+x, w*k
i += 1; j += 2; k += j
q, r, s, t = q*u+r*w, q*v+r*x, s*u+t*w, s*v+t*x
def g3_ref():
# Gosper, reference
q, r, t = mpz(1), mpz(180), mpz(60)
i = 2
while True:
u, y = i*(i*27+27)+6, (q+r)/t
yield y
q, r, t, i = 10*q*i*(2*i-1), 10*u*(q*(5*i-2)+r-y*t), t*u, i+1
def g3_md():
# Gosper, multi-digit
q, r, t = mpz(1), mpz(0), mpz(1)
i, j = 1, 60
z = mpz(10)**50
while True:
n = (q+r)/t
if n*t > 6*i*q+r-t:
for d in digits(n, i>38 and 50 or 1): yield d
q, r = z*q, z*(r-n*t)
u, v, x = 1, 0, 1
for k in range(37):
u, v, x = u*i*(2*i-1), j*(u*(5*i-2)+v), x*j
i += 1; j += 54*i
q, r, t = q*u, q*v+r*x, t*x
def g4_md():
# Ramanujan 39, multi-digit
q, r, s ,t = mpz(0), mpz(3528), mpz(1), mpz(0)
i = 1
z = mpz(10)**3511
while True:
n = (q+r)/(s+t)
if n == (22583*i*q+r)/(22583*i*s+t):
for d in digits(n, i>597 and 3511 or 1): yield d
q, r = z*(q-n*s), z*(r-n*t)
u, v, x = mpz(1), mpz(0), mpz(1)
for k in range(596):
c, d, f = i*(i*(i*32-48)+22)-3, 21460*i-20337, -i*i*i*24893568
u, v, x = u*c, (u*d+v)*f, x*f
i += 1
q, r, s, t = q*u, q*v+r*x, s*u, s*v+t*x
def digits(x, n):
o = []
for k in range(n):
x, r = divmod(x, 10)
o.append(r)
return reversed(o)
ノート
上記は6つの実装です。作成者が提供する2つのリファレンス実装(_ref
)と、一括で用語を計算し、一度に複数の数字を生成する4 つのリファレンス実装(_md
)です。すべての実装は100,000桁で確認されています。バッチサイズを選択するとき、時間の経過とともに徐々に精度が失われる値を選択しました。たとえばg1_md
、33回の反復で、バッチごとに10桁を生成します。ただし、これは〜9.93の正しい数字のみを生成します。精度がなくなると、チェック条件が失敗し、追加のバッチが実行されます。これは、時間の経過とともに徐々に余分に必要とされる不必要な精度よりもパフォーマンスが高いようです。
- g1(ライプニッツ/オイラー)を表す
追加の変数j
が保持され2*i+1
ます。著者はリファレンス実装でも同じことを行います。n
個別の計算は、次ではなくq
、r
およびの現在の値を使用するため、はるかに簡単です(わかりにくくなります)t
。
- g2(Lambert)
チェックn == q/s
は明らかにかなり緩いです。これは読むべきn == (q*(k+2*j+4)+r)/(s*(k+2*j+4)+t)
ところ、j
ある2*i-1
とk
されますi*i
。反復回数が増えるr
と、t
用語と用語の重要性はますます低くなります。そのままで、最初の100,000桁に適しているので、おそらくすべてに適しています。著者はリファレンス実装を提供していません。
- g3(Gosper)
著者はn
、後続の反復で変化しないことを確認する必要はなく、アルゴリズムを遅くするだけであると推測します。おそらく本当ですが、ジェネレーターは現在生成されているよりも約13%多くの正しい数字を保持しています。チェックインを追加し直し、50桁が正しくなるまで待ち、それらを一度に生成し、パフォーマンスを著しく向上させました。
- G4(ラマヌジャン39)
として計算
残念なことには、s
原因初期(3528÷)組成物に、ゼロにしませんが、それはG3よりかなり速く、まだです。収束は用語ごとに〜5.89桁で、3511桁が一度に生成されます。それが少し多ければ、46回の反復ごとに271桁を生成することもまともな選択です。
タイミング
比較のみを目的として、私のシステムで撮影しました。時間は秒単位でリストされます。タイミングに10分以上かかった場合、それ以上のテストは実行しませんでした。
| g1_ref | g1_md | g2_md | g3_ref | g3_md | g4_md
------------+---------+---------+---------+---------+---------+--------
10,000 | 1.645 | 0.229 | 0.093 | 0.312 | 0.062 | 0.062
20,000 | 6.859 | 0.937 | 0.234 | 1.140 | 0.250 | 0.109
50,000 | 55.62 | 5.546 | 1.437 | 9.703 | 1.468 | 0.234
100,000 | 247.9 | 24.42 | 5.812 | 39.32 | 5.765 | 0.593
200,000 | 2,158 | 158.7 | 25.73 | 174.5 | 33.62 | 2.156
500,000 | - | 1,270 | 215.5 | 3,173 | 874.8 | 13.51
1,000,000 | - | - | 1,019 | - | - | 58.02
収束速度が遅いにもかかわらず、g2
最終的に追い越すのは興味深いことg3
です。これは、オペランドが大幅に遅い速度で成長し、長期的には勝つためだと思われます。最速の実装g4_md
はg3_ref
、500,000桁の実装よりも約235倍高速です。そうは言っても、このように数字をストリーミングすることには依然として大きなオーバーヘッドがあります。Ramanujan 39(python source)を使用してすべての数字を直接計算すると、約10倍高速になります。
なぜチュドノフスキーではないのですか?
Chudnovskyアルゴリズムは完全な精度の平方根を必要としますが、それがどのように機能するのかについては正直にわかりません。ラマヌジャン39は、この点で多少特殊です。ただし、この方法は、y-cruncherで使用されるようなMachinのような式に役立つように思われるため、探索する価値のある方法かもしれません。