Calcular π com convergência quadrática


20

Escreva uma função ou programa completo que leva um número positivo ne executa npassos de um algoritmo iterativo para calcular π que tem convergência quadrática (ou seja, aproximadamente dobra o número de dígitos precisos em cada iteração), em seguida, retorna ou imprime 2 n dígitos corretos (incluindo o começo 3). Um desses algoritmos é o algoritmo de Gauss – Legendre , mas você pode usar outro algoritmo, se preferir.

Exemplos:

entrada 1→ saída 3.1
entrada 2→ saída 3.141
entrada 5→ saída3.1415926535897932384626433832795

Requisitos:

  • Cada iteração do algoritmo deve executar um número constante de operações básicas, como adição, subtração, multiplicação, divisão, potência e raiz (com expoente inteiro / grau) - cada operação em números "grandes" inteiros / decimais é contada como uma única se envolver um ou mais loops internamente. Para ser claro, funções trigonométricas e potências envolvendo números complexos não são operações básicas.
  • Espera-se que o algoritmo tenha uma etapa de inicialização que também deve ter um número constante de operações.
  • Se o algoritmo precisar de 1 ou 2 mais iterações para obter 2 n dígitos corretos, você pode executar até n+2iterações em vez de apenas n.
  • Se não estiver claro o suficiente, após os 2 n dígitos corretos , seu programa não deverá imprimir mais nada (como dígitos mais corretos, dígitos errados ou as obras completas de Shakespeare).
  • Seu programa deve suportar valores de n1 a pelo menos 20.
  • Seu programa não deve demorar mais de uma hora para n= 20 em um computador moderno (não é uma regra difícil, mas tente mantê-lo razoável).
  • O programa não deve obter mais de 20 dígitos precisos após a inicialização e a primeira iteração do algoritmo.
  • O programa deve ser executável no Linux usando software disponível gratuitamente.
  • O código fonte deve usar apenas caracteres ASCII.

Pontuação:

Código simples de golfe, o código mais curto vence.

Vencedora:

O vencedor é o Digital Trauma, eu finalmente terminei de executar o código dele em n = 20 (apenas brincando). Prêmio especial vai para primo por sua solução python muito rápida e algoritmo diferente :)


1
Convergência quadrática é erro ~ N ^ (1/2) . O que você descreve é erro de convergência exponencial ~ 2 ^ (- N) .
yo

@yo 'você está dizendo que a wikipedia está errada?
Aditsu 18/03/2015

1
Enganador, pelo menos para dizer: "convergência quadrática" está de ~q^(n^2)acordo com a 1ª seção lá e de ~q^2acordo com a 2ª seção lá.
yo

1
Eu não entendo codegolf: certamente alguém poderia simplesmente escrever sua própria linguagem de programação especificamente para uma única tarefa como essa, e depois escrever um programa de, digamos, 0 bytes?
18715 theellygusti

2
@theonlygusti que seria uma brecha padrão e iria ficar desclassificado
aditsu

Respostas:


14

dc, 99 bytes

Golfe:

2?dsi1+^k1dddsa2v/sb4/stsp[lalb*vlalb+2/dla-d*lp*ltr-stsasblp2*spli1-dsi0<m]dsmxK2/1-klalb+d*4lt*/p

Com espaço em branco e comentários para "legibilidade":

2?dsi               # Push 2. push input n, duplicate and store in i
1+^k                # Set calculation precision to 2^(n+1)
1dddsa              # Push four 1s. Store 1st in a
2v/sb               # Store 1/sqrt(2) in b
4/st                # Store 1/4 in t
sp                  # Store 1 in p
[                   # Start iteration loop macro
lalb*v              # Save sqrt(a*b) on stack
lalb+2/d            # Save a[i+1] = (a[i]+b[i])/2 on stack and duplicate
la-d*lp*ltr-        # Save t-p(a[i]-a[i+1])^2 on the stack
st                  # Store t result from stack
sa                  # Store a result from stack
sb                  # Store b result from stack
lp2*sp              # Store 2p in p
li1-dsi0<m]         # Decrement iteration counter i; recurse into macro if < 0
dsmx                # Duplicate, store and run macro
K2/1-k              # Set display precision to 2^n-1
lalb+d*4lt*/        # Save (a+b)^2/4t on stack
p                   # Print result

dcprecisa saber quantos dígitos de precisão devem ser usados. A precisão do cálculo precisa ser maior que a precisão da exibição final, portanto, a precisão do cálculo é definida como 2^(n+1)dígitos. Eu verifiquei a precisão da saída com n = 10 em relação a http://www.angio.net/pi/digits/pi1000000.txt .

Isso diminui drasticamente para n maior; n = 12 leva 1,5 minutos na minha VM. A execução de algumas amostras diferentes mostra que a complexidade do tempo é O (e ^ n) (não é surpreendente). Extrapolar para n = 20 teria uma duração de 233 dias. Ah bem. Melhor que a morte pelo calor do universo, pelo menos.

Isso é moderado - a pilha é usada para eliminar variáveis ​​temporárias durante os cálculos de cada iteração, mas é possível que haja mais uso da pilha para encurtar isso mais.

$ dc glpi.dc <<< 1
3.1
$ dc glpi.dc <<< 2
3.141
$ dc glpi.dc <<< 5
3.1415926535897932384626433832795
$ time dc glpi.dc <<< 7
3.1415926535897932384626433832795028841971693993751058209749445923078\
164062862089986280348253421170679821480865132823066470938446

real    0m0.048s
user    0m0.039s
sys 0m0.000s
$ 

Se você não gosta de dcagrupar a saída em 70 caracteres, pode definir a variável de ambiente DC_LINE_LENGTHcomo 0:

$ DC_LINE_LENGTH=0 dc glpi.dc <<< 8
3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481117450284102701938521105559644622948954930381964428810975665933446128475648233786783165271201909145648
$ 

2
Haha, "legibilidade". Realmente não se aplica ao dc. ;)
Alex A.

Parece imprimir muito mais do que 32 dígitos para a entrada 5
aditsu 18/03/2015

Eu adicionei uma regra para isso, além de outra sobre o tempo de execução (mas não realmente rigorosa). Também não gosto de como sua saída é dividida em várias linhas com barras invertidas, isso é uma limitação de dc?
aditsu 18/03/2015

Receio que a saída é errado para n = 6
aditsu

1
Ótimo, e você também tem menos de 100 :) Você também pode postar o programa de 99 caracteres com golf sem espaços em branco / comentários?
Aditsu 18/03/2015

10

R, 156 bytes

Vamos começar esta festa ... com a implementação absolutamente ingênua do algoritmo de Gauss-Legendre de todos os tempos.

for(i in 1:scan()){if(i<2){a=p=Rmpfr::mpfr(1,2e6);t=a/4;b=t^t}else{x=(a+b)/2;b=(a*b)^.5;t=t-p*(a-x)^2;a=x;p=2*p};o=(a+b)^2/(4*t)};cat(Rmpfr::format(o,2^i))

Ungolfed + explicação:

# Generate n approximations of pi, where n is read from stdin
for (i in 1:scan()) {

    # Initial values on the first iteration
    if (i < 2) {
        a <- p <- Rmpfr::mpfr(1, 1e7)
        t <- a/4
        b <- t^t
    } else {
        # Compute new values
        x <- (a + b) / 2
        b <- (a*b)^0.5
        t <- t - p*(a - x)^2

        # Store values for next iteration
        a <- x
        p <- 2*p
    }

    # Approximate pi 
    o <- (a + b)^2 / (4*t)
}

# Print the result with 2^n digits
cat(Rmpfr::format(o, 2^i))

A mpfr()função faz parte do Rmpfrpacote. Ele cria um mpfrobjeto usando o primeiro argumento como valor e o segundo argumento como o número de bits de precisão. Atribuímos ae pa 1 e, definindo com tbase em a(eb base em t) o mpfrtipo propaga para todas as quatro variáveis, mantendo assim a precisão por toda parte.

Conforme mencionado, isso requer o pacote R Rmpfr, que é um acrônimo para "Ponto flutuante de precisão múltipla R confiável". O pacote usa o GMP em segundo plano. Infelizmente, a base R não tem a capacidade de fazer aritmética de alta precisão, daí a dependência do pacote.

Não tem Rmpfr? Sem suor.install.packages("Rmpfr")e todos os seus sonhos se tornarão realidade.

Observe que 2e6foi especificado como a precisão. Isso significa que temos 2.000.000 de bits de precisão, o que é suficiente para manter a precisão por pelo menos n= 20. (Nota:n = 20 leva muito tempo, mas menos de uma hora no meu computador).

A abordagem aqui é literalmente apenas uma regurgitação das fórmulas na página da Wikipedia, mas, ei, temos que começar em algum lugar.

Qualquer entrada é bem-vinda, como sempre!


Eu tive que reescrever muito disso, mas ainda tenho que reconhecer que Peter Taylor me ajudou a tirar 70 bytes da minha primeira pontuação. Nas palavras do DigitalTrauma, "boom".


7

Python 2, 214 bytes

Esse desafio apresentou uma boa desculpa para eu aprender o módulo Decimal. Os números decimais têm precisão definível e suporte de raiz quadrada. Eu configurei a precisão para uma estimativa conservadora da precisão, dependendo da contagem de loops.

Atualizar

Atualizei o programa para melhorar a precisão e a velocidade, às custas do golfe. Usando o sqrt()método decimal e substituindo o x**2uso x*x, agora é 200 vezes mais rápido. Isso significa que agora ele pode calcular 20 loops, resultando em um milhão de dígitos em 6,5 horas. Os números decimais geralmente apresentam um erro no último dígito (causado por operações no limite de precisão); portanto, o programa agora usa e descarta 5 dígitos extras para que apenas dígitos precisos sejam impressos.

from decimal import*
d=Decimal
e=input()
getcontext().prec=5+(1<<e)
k=d(1)
j=d(2)
g=j*j
h=k/j
a=k
b=k/j.sqrt()
t=k/g
p=k
for i in[0]*e:f=a;a,b=(a+b)/j,(a*b).sqrt();c=f-a;t-=c*c*p;p+=p
l=a+b
print str(l*l/g/t)[:-5]

Exemplo de execução:

$ echo 1 | python min.py 
3.1
$ echo 2 | python min.py 
3.141
$ echo 3 | python min.py 
3.1415926
$ echo 5 | python min.py 
3.1415926535897932384626433832795
$ echo 12 | python min.py
3.141592653589793238462643383279502884197169399375105820974944592307816406286208
99862803482534211706798214808651328230664709384460955058223172535940812848111745
02841027019385211055596446229489549303819644288109756659334461284756482337867831
65271201909145648566923460348610454326648213393607260249141273724587006606315588
17488152092096282925409171536436789259036001133053054882046652138414695194151160
94330572703657595919530921861173819326117931051185480744623799627495673518857527
24891227938183011949129833673362440656643086021394946395224737190702179860943702
77053921717629317675238467481846766940513200056812714526356082778577134275778960
91736371787214684409012249534301465495853710507922796892589235420199561121290219
60864034418159813629774771309960518707211349999998372978049951059731732816096318
59502445945534690830264252230825334468503526193118817101000313783875288658753320
83814206171776691473035982534904287554687311595628638823537875937519577818577805
32171226806613001927876611195909216420198938095257201065485863278865936153381827
96823030195203530185296899577362259941389124972177528347913151557485724245415069
59508295331168617278558890750983817546374649393192550604009277016711390098488240
12858361603563707660104710181942955596198946767837449448255379774726847104047534
64620804668425906949129331367702898915210475216205696602405803815019351125338243
00355876402474964732639141992726042699227967823547816360093417216412199245863150
30286182974555706749838505494588586926995690927210797509302955321165344987202755
96023648066549911988183479775356636980742654252786255181841757467289097777279380
00816470600161452491921732172147723501414419735685481613611573525521334757418494
68438523323907394143334547762416862518983569485562099219222184272550254256887671
79049460165346680498862723279178608578438382796797668145410095388378636095068006
42251252051173929848960841284886269456042419652850222106611863067442786220391949
45047123713786960956364371917287467764657573962413890865832645995813390478027590
09946576407895126946839835259570982582262052248940772671947826848260147699090264
01363944374553050682034962524517493996514314298091906592509372216964615157098583
87410597885959772975498930161753928468138268683868942774155991855925245953959431
04997252468084598727364469584865383673622262609912460805124388439045124413654976
27807977156914359977001296160894416948685558484063534220722258284886481584560285
06016842739452267467678895252138522549954666727823986456596116354886230577456498
03559363456817432411251507606947945109659609402522887971089314566913686722874894
05601015033086179286809208747609178249385890097149096759852613655497818931297848
21682998948722658804857564014270477555132379641451523746234364542858444795265867
82105114135473573952311342716610213596953623144295248493718711014576540359027993
44037420073105785390621983874478084784896833214457138687519435064302184531910484
81005370614680674919278191197939952061419663428754440643745123718192179998391015
91956181467514269123974894090718649423196156794520809514655022523160388193014209
37621378559566389377870830390697920773467221825625996615014215030680384477345492
02605414665925201497442850732518666002132434088190710486331734649651453905796268
56100550810665879699816357473638405257145910289706414011097120628043903975951567
71577004203378699360072305587631763594218731251471205329281918261861258673215791
98414848829164470609575270695722091756711672291098169091528017350671274858322287
18352093539657251210835791513698820914442100675103346711031412671113699086585163
98315019701651511685171437657618351556508849099898599823873455283316355076479185
35893226185489632132933089857064204675259070915481416549859461637180270981994309
92448895757128289059232332609729971208443357326548938239119325974636673058360414
28138830320382490375898524374417029132765618093773444030707469211201913020330380
19762110110044929321516084244485963766983895228684783123552658213144957685726243
34418930396864262434107732269780280731891544110104468232527162010526522721116603
96665573092547110557853763466820653109896526918620564769312570586356620185581007
29360659876486117

O código não destruído:

from decimal import *
d = Decimal

loops = input()
# this is a conservative estimate for precision increase with each loop:
getcontext().prec = 5 + (1<<loops)

# constants:
one = d(1)
two = d(2)
four = two*two
half = one/two

# initialise:
a = one
b = one / two.sqrt()
t = one / four
p = one

for i in [0]*loops :
    temp = a;
    a, b = (a+b)/two, (a*b).sqrt();
    pterm = temp-a;
    t -= pterm*pterm * p;
    p += p

ab = a+b
print str(ab*ab / four / t)[:-5]

4
Hehhalf = one/two
Trauma digital

Parece que você não está imprimindo o número correto de dígitos. E eu me pergunto se a lentidão se deve ao uso desnecessário de **.
Aditsu 18/03/2015

1
@aditsu, reduzi a precisão à contagem esperada de dígitos (mas jogar fora uma precisão perfeitamente boa de uma iteração está fazendo meus dentes coçarem). Boa sugestão sobre o **efeito. Encontrei muita velocidade ao me livrar deles. Não consigo encontrar os 20 loops em 1 hora. Talvez com pypy ou Cython? Hummm. Eu vou considerar isso.
Logic Knight

Muito melhor :) Para esse problema, jogar fora uma boa precisão é menos ruim do que continuar com uma precisão ruim. O limite de 1 hora é baseado no meu código de teste cjam / java executado com java 8. Talvez o python não tenha uma multiplicação / divisão / sqrt eficiente para grandes números (Karatsuba & co)?
Aditsu 18/03/2015

@aditsu: Eu acredito que números inteiros têm karatsuba (e apenas isso) - mas com tamanho de membro de 32 bits em vez de tamanho de membro de 64 bits. Quem sabe sobre decimal.

5

Python (2.7) - 131 bytes

from gmpy import*
n=input()
p=a=fsqrt(mpf(8,4<<n));b=0
exec"a=fsqrt(a/2);b=1/(a-a*b+b/a+1);p*=b+a*a*b;a+=1/a;"*n
print`p`[5:2**n+6]

Atualização: agora usando em gmpyvez de gmpy2. Por qualquer motivo, a gmpy2definição da precisão em um único valor não se propaga para outros valores. O resultado de qualquer cálculo volta à precisão do contexto atual. A precisão se propaga gmpy, o que me parece mais intuitivo. Também é consideravelmente menos detalhado.

Usando um dos muitos algoritmos criados por Borwein e Borwein , levemente refatorado. n = 20 leva cerca de 11 segundos na minha caixa. Não é o método mais eficiente, mas ainda não é ruim.


Refatoração

O algoritmo original era o seguinte:




A refatoração foi feita de forma incremental, mas o resultado final é que




A principal simplificação ocorre em p n + 1 . Também é um pouco mais rápido devido à eliminação de uma divisão.

A implementação atual empurra uma iteração n back no cálculo de p n + 1 , o que permite uma inicialização diferente de p 0 ( 2√2 ), mas é idêntico.


Uso da amostra

$ echo 1 | python pi-borwein.py
3.1

$ echo 2 | python pi-borwein.py
3.141

$ echo 5 | python pi-borwein.py
3.1415926535897932384626433832795

$ echo 10 | python pi-borwein.py
3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481117450284102701938521105559644622948954930381964428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249141273724587006606315588174881520920962829254091715364367892590360011330530548820466521384146951941511609433057270365759591953092186117381932611793105118548074462379962749567351885752724891227938183011949129833673362440656643086021394946395224737190702179860943702770539217176293176752384674818467669405132000568127145263560827785771342757789609173637178721468440901224953430146549585371050792279689258923542019956112129021960864034418159813629774771309960518707211349999998372978049951059731732816096318595024459455346908302642522308253344685035261931188171010003137838752886587533208381420617177669147303598253490428755468731159562863882353787593751957781857780532171226806613001927876611195909216420198938095257201065485863278

Ótimo, mas está faltando um dígito para n = 7
aditsu 18/03/2015

Além disso, é este algoritmo ?
Aditsu 18/03/2015

@aditsu corrigido, e sim é.
Primo

Agora, o último dígito está errado para n = 5
aditsu 18/03/2015

1
O @aditsu pip install gmpytrabalhou para mim; gmpye gmpy2são pacotes separados. No entanto, depende do uso obsoleto distutils.
Primo 25/03

3

bc e método de Newton, 43 bytes

O método de Newton para encontrar zeros de qualquer função converge quadraticamente e o algoritmo é bem mais simples do que para Gauss-Legendre. Basicamente, tudo se resume a:

xnew = xold - f (xold) / f '(xold)

Então, aqui está o trecho de acordo:

n=20;x=3;scale=2^n;while(n--)x-=s(x)/c(x);x

Um pouco mais legível:

/* desired number of iterations */
n = 20;

/* starting estimate for pi */
x = 3;

/* set precision to 2^n */
scale = 2^n;

/* perform n iteration steps */
while(n--)
  // f:=sin, f'=cos
  x -= s(x)/c(x)

Para testar isso, execute bc -lem um shell e cole o snippet acima. Esteja preparado para esperar um pouco; n=20está em execução há cerca de 5 minutos e ainda não tem fim à vista. n=10leva cerca de 40 anos.


4
Não tenho certeza se o seno e o cosseno se qualificam como "operações básicas como adição, subtração, multiplicação, divisão e poder (incluindo raízes)" . No entanto, se você rolou seu próprio seno / cosseno, isso provavelmente seria aceitável.
Primo

1
Fórmula puro, embora - se diz que π é um ponto fixo de f (x) = x - tan (x)
Casey Chu
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.