파이썬 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
이를 위해 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의 Digits에 대한 Unbounded Spigot Algorims 논문에서 직접 가져온 무제한 생성기를 사용합니다 . 저자에 따르면, 제공된 구현은 "의도적으로 모호하다"며, 의도적으로 난독 화없이 저자가 제시 한 세 가지 알고리즘을 모두 새로 구현하기로 결정했습니다. 또한 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 가지 구현은 저자가 제공 한 2 개의 참조 구현 (으로 표시 _ref
)과 4 개는 일괄 적으로 용어를 계산하여 한 번에 여러 자릿수를 생성하는 것입니다 ( _md
). 모든 구현은 100,000 자리로 확인되었습니다. 배치 크기를 선택할 때 시간이 지남에 따라 천천히 정밀도가 떨어지는 값을 선택했습니다. 예를 들어, g1_md
배치 당 10 자리를 생성하고 33 회 반복합니다. 그러나 이것은 ~ 9.93 정확한 숫자 만 생성합니다. 정밀도가 떨어지면 검사 조건이 실패하고 추가 배치가 실행됩니다. 이것은 시간이 지남에 따라 천천히 불필요한 추가 정밀도보다 더 성능이 좋은 것으로 보입니다.
- g1 (Leibniz / Euler)를 나타내는
추가 변수 j
가 유지됩니다 2*i+1
. 저자는 참조 구현에서 동일한 작업을 수행합니다. 계산 n
이의 현재 값을 사용하기 때문에 별도로하는 것은 훨씬 간단 (덜 모호한)이며 q
, r
그리고 t
오히려 다음보다.
- g2 (황색)
수표 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)
불행히도
계산 되었으나 초기 (3528 ÷) 구성으로 인해 0으로 끝나지 않지만 여전히 g3보다 훨씬 빠릅니다. 수렴은 용어 당 ~ 5.89 자리이며 한 번에 3511 자리가 생성됩니다. 그것이 조금 많으면 46 반복마다 271 자리를 생성하는 것도 괜찮은 선택입니다.
s
타이밍
비교 목적으로 만 내 시스템에서 촬영했습니다. 시간은 초 단위로 나열됩니다. 타이밍이 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 알고리즘에는 완전 정밀 제곱근이 필요합니다. 정확히 제곱근을 사용한다고 가정합니다. Ramanujan 39는 이와 관련하여 다소 특별합니다. 그러나이 방법은 y-cruncher에서 사용하는 것과 같은 Machin과 같은 공식에 도움이 될 것 같으므로 탐색 할 가치가 있습니다.