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约需2s即可完成10,000位数。
样品用量
$ 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
为此,我已经从Chudnovsky切换到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的数字的无界子弹算法》中获取。根据作者的说法,所提供的实现是“故意模糊的”,因此,我决定对作者列出的所有三种算法进行全新的实现,而无需进行故意的混淆。我还添加了第四个,基于Ramanujan#39。
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种实现:作者提供的两个参考实现(表示为_ref
),以及四个批量计算项(一次生成多个数字_md
)的参考实现。已确认所有实现均为100,000位数字。选择批次大小时,我选择的值会随着时间的流逝逐渐失去精度。例如,g1_md
每批生成10位数字,并进行33次迭代。但是,这只会产生〜9.93个正确的数字。当精度用完时,检查条件将失败,从而触发要运行的额外批处理。这似乎比随着时间的流逝逐渐获得多余的,不必要的精度要好得多。
- g1(Leibniz / Euler)保留
一个额外的变量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(Ramanujan 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?
Chudnovsky算法需要一个全精度的平方根,老实说,我不确定该如何使用-假设根本不可能。拉马努詹39在这方面有些特殊。但是,该方法似乎确实有助于诸如y-cruncher使用的类似Machin的公式,因此这可能是一个值得探索的途径。