Calcular π con convergencia cuadrática


20

Escriba una función o un programa completo que tome un número positivo ny realice los npasos de un algoritmo iterativo para calcular π que tiene convergencia cuadrática (es decir, aproximadamente duplica el número de dígitos exactos en cada iteración) y luego devuelve o imprime 2 n dígitos correctos (incluyendo el principio 3). Uno de estos algoritmos es el algoritmo Gauss-Legendre , pero puede utilizar un algoritmo diferente si lo prefiere.

Ejemplos:

entrada 1→ salida 3.1
entrada 2→ salida 3.141
entrada 5→ salida3.1415926535897932384626433832795

Requisitos:

  • Cada iteración del algoritmo debe realizar un número constante de operaciones básicas tales como suma, resta, multiplicación, división, potencia y raíz (con exponente / grado entero): cada operación de este tipo en números enteros / decimales "grandes" se cuenta como uno incluso si involucra uno o más bucles internamente. Para ser claros, las funciones trigonométricas y las potencias que involucran números complejos no son operaciones básicas.
  • Se espera que el algoritmo tenga un paso de inicialización que también debe tener un número constante de operaciones.
  • Si el algoritmo necesita 1 o 2 iteraciones más para llegar a 2 n dígitos correctos, puede realizar hasta n+2iteraciones en lugar de solo n.
  • Si no fue lo suficientemente claro, después de los 2 n dígitos correctos , su programa no debe imprimir nada más (como dígitos más correctos, dígitos incorrectos o los trabajos completos de Shakespeare).
  • Su programa debe admitir valores de n1 a al menos 20.
  • Su programa no debería tomar más de una hora por n= 20 en una computadora moderna (no es una regla difícil, pero trate de mantenerlo razonable).
  • El programa no debe obtener más de 20 dígitos exactos después de la inicialización y la primera iteración del algoritmo.
  • El programa debe ser ejecutable en Linux utilizando software disponible gratuitamente.
  • El código fuente debe usar solo caracteres ASCII.

Puntuación:

Código de golf sencillo, el código más corto gana.

Ganador:

El ganador es Digital Trauma, finalmente terminé de ejecutar su código en n = 20 (es broma). Premio especial para primo por su solución Python muy rápida y algoritmo diferente :)


1
La convergencia cuadrática es error ~ N ^ (1/2) . Lo que describe es un error de convergencia exponencial ~ 2 ^ (- N) .
yo '

@yo '¿estás diciendo que wikipedia está mal?
aditsu

1
Engañoso, al menos para decir: "convergencia cuadrática" es de ~q^(n^2)acuerdo con la primera sección allí y de ~q^2acuerdo con la segunda sección allí.
yo '

1
No entiendo codegolf: seguramente cualquiera podría escribir su propio lenguaje de programación específicamente para una sola tarea como esta, y luego escribir un programa de, digamos, 0 bytes.
theonlygusti

2
@theonlygusti sería una escapatoria estándar y sería descalificado
aditsu

Respuestas:


14

dc, 99 bytes

Golfizado:

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

Con espacios en blanco y comentarios para "legibilidad":

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

dcnecesita saber cuántos dígitos de precisión deben usarse. La precisión del cálculo debe ser mayor que la precisión de la pantalla final, por lo que la precisión del cálculo se establece en 2^(n+1)dígitos. He verificado la precisión de la salida con n = 10 contra http://www.angio.net/pi/digits/pi1000000.txt .

Esto se ralentiza dramáticamente para n mayor; n = 12 toma 1.5 minutos en mi VM. La ejecución de algunas muestras diferentes muestra que la complejidad del tiempo es O (e ^ n) (no es sorprendente). Extrapolar esto a n = 20 tendría un tiempo de ejecución de 233 días. Oh bien. Mejor que la muerte por calor del universo al menos.

Esto es moderado: la pila se usa para eliminar variables temporales durante los cálculos de cada iteración, pero posiblemente haya más uso de la pila para acortar esto más.

$ 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
$ 

Si no le gusta dcajustar la salida a 70 caracteres, puede establecer la variable de entorno DC_LINE_LENGTHen 0:

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

2
Jaja, "legibilidad". Realmente no se aplica a DC. ;)
Alex A.

Parece imprimir mucho más de 32 dígitos para la entrada 5
aditsu

Agregué una regla para eso, más otra sobre el tiempo de ejecución (pero no realmente estricta). Tampoco me gusta cómo su salida se divide en varias líneas con barras invertidas, ¿es eso una limitación de CC?
aditsu

Me temo que la salida es incorrecta para n = 6
aditsu

1
Genial, y también lo obtuviste por debajo de 100 :) ¿Podrías publicar el programa de golf de 99 caracteres sin espacios en blanco / comentarios?
aditsu

10

R, 156 bytes

Comencemos esta fiesta ... con la implementación absolutamente ingenua del algoritmo Gauss-Legendre.

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 + explicación:

# 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))

La mpfr()función es parte del Rmpfrpaquete. Crea un mpfrobjeto utilizando el primer argumento como valor y el segundo argumento como el número de bits de precisión. Asignamos aya p1, y al definir en tbase a a(y en bbase a t), el mpfrtipo se propaga a las cuatro variables, manteniendo así la precisión en todo momento.

Como se mencionó, esto requiere el paquete R Rmpfr , que es un acrónimo de "Punto flotante de precisión múltiple R confiable". El paquete usa GMP en segundo plano. Lamentablemente, la base R no tiene la capacidad de realizar operaciones aritméticas de alta precisión, de ahí la dependencia del paquete.

No tiene Rmpfr? Sin sudar.install.packages("Rmpfr")y todos tus sueños se harán realidad.

Tenga en cuenta que 2e6se especificó como la precisión. Eso significa que tenemos 2,000,000 bits de precisión, que es suficiente para mantener la precisión durante al menos n= 20. (Nota:n = 20 lleva mucho tiempo pero menos de una hora en mi computadora).

El enfoque aquí es, literalmente, solo una regurgitación de las fórmulas en la página de Wikipedia, pero bueno, tenemos que comenzar en alguna parte.

Cualquier entrada es bienvenida como siempre!


Tuve que reescribir mucho de esto, pero aún tengo que reconocer que Peter Taylor me ayudó a eliminar 70 bytes de mi primer puntaje. En palabras de DigitalTrauma, "boom".


7

Python 2, 214 bytes

Este desafío me presentó una buena excusa para aprender el módulo Decimal. Los números decimales tienen precisión definible y tienen soporte de raíz cuadrada. He establecido la precisión en una estimación conservadora de la precisión según el recuento de bucles.

Actualizar

He actualizado el programa para mejorar la precisión y la velocidad, a expensas del golf. Al usar el sqrt()método decimal y reemplazar el x**2uso con x*x, ahora es 200 veces más rápido. Esto significa que ahora puede calcular 20 bucles que dan un resultado de un millón de dígitos en 6.5 horas. Los números decimales a menudo tienen un error en el último dígito (causado por operaciones en el límite de precisión), por lo que el programa ahora usa y descarta 5 dígitos adicionales para que solo se impriman los dígitos exactos.

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]

Ejecución de muestra:

$ 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

El código sin golf:

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]

44
Jehalf = one/two
Trauma digital

Parece que no está imprimiendo la cantidad correcta de dígitos. Y me pregunto si la lentitud se debe al uso innecesario de **.
aditsu

1
@aditsu, he reducido la precisión al recuento de dígitos esperado (pero descartar una precisión perfectamente buena de una iteración me está haciendo picar los dientes). Buena sugerencia sobre el **efecto. Encontré mucha velocidad al deshacerme de ellos. Sin embargo, no puedo cumplir los 20 bucles en 1 hora. Tal vez con pypy o Cython? Hmmm Lo consideraré.
Logic Knight

Mucho mejor :) Para este problema, desechar una buena precisión es menos malo que continuar con una mala precisión. El límite de 1 hora se basa en mi código de prueba cjam / java ejecutado con java 8. ¿Quizás python no tiene una multiplicación / división / sqrt eficiente para números grandes (Karatsuba & co)?
aditsu

@aditsu: Creo que los enteros tienen karatsuba (y solo eso), pero con un tamaño de extremidad de 32 bits en lugar de un tamaño de extremidad de 64 bits. Quién 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]

Actualización: ahora usando en gmpylugar de gmpy2. Por alguna razón, al gmpy2establecer la precisión en un solo valor no se propaga a otros valores. El resultado de cualquier cálculo vuelve a la precisión del contexto actual. La precisión se propaga engmpy , lo que me parece más intuitivo. También es considerablemente menos detallado.

Usando uno de los muchos algoritmos ideados por Borwein y Borwein , ligeramente refactorizados. n = 20 toma alrededor de 11 segundos en mi caja. No es el método más eficiente, pero tampoco está mal.


Refactorización

El algoritmo original fue el siguiente:




La refactorización se realizó de forma incremental, pero el resultado final es que




La mayor simplificación ocurre en p n + 1 . También es un poco más rápido debido a haber eliminado una división.

La implementación actual hace retroceder una n una iteración en el cálculo de p n + 1 , lo que permite una inicialización diferente de p 0 ( 2√2 ), pero por lo demás es idéntica.


Uso de muestra

$ 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

Genial, pero te falta un dígito para n = 7
aditsu

Además, ¿es este algoritmo ?
aditsu

@aditsu arreglado, y sí lo es.
primo

Ahora el último dígito está mal para n = 5
aditsu

1
@aditsu pip install gmpytrabajó para mí; gmpyy gmpy2son paquetes separados. Sin embargo, se basa en lo obsoleto distutils.
primo

3

aC y el método de Newton, 43 bytes

El método de Newton para encontrar ceros de cualquier función converge cuadráticamente y el algoritmo es mucho más simple que para Gauss-Legendre. Básicamente se reduce a:

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

Así que aquí va el fragmento correspondiente:

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

Un poco más legible:

/* 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 probar esto, ejecute bc -len un shell y pegue el fragmento anterior. Prepárate para esperar un rato; n=20ha estado funcionando durante unos 5 minutos y todavía no hay un final a la vista. n=10toma alrededor de 40 s.


44
No estoy seguro si el seno y el coseno califican como "operaciones básicas como la suma, resta, multiplicación, división y potencia (incluidas las raíces)" . Sin embargo, si rodó su propio seno / coseno, eso probablemente sería aceptable.
primo

1
Sin embargo, fórmula ordenada: dice que π es un punto fijo de f (x) = x - tan (x)
Casey Chu
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.