Analizo tu código en la sección Analizando tu código . Antes de eso, presento un par de divertidas secciones de material extra.
One liner One letter 1
say e; # 2.718281828459045
Haga clic en el enlace de arriba para ver el extraordinario artículo de Damian Conway sobre computación e
en Raku.
El artículo es muy divertido (después de todo, es Damian). Es una discusión muy comprensible de la informática e
. Y es un homenaje a la reencarnación de bicarbonato de Raku de la filosofía TIMTOWTDI adoptada por Larry Wall. 3
Como aperitivo, aquí hay una cita de aproximadamente la mitad del artículo:
Dado que todos estos métodos eficientes funcionan de la misma manera, sumando (un subconjunto inicial de) una serie infinita de términos, tal vez sería mejor si tuviéramos una función para hacer eso por nosotros. Y ciertamente sería mejor si la función pudiera funcionar por sí misma exactamente cuánto de ese subconjunto inicial de la serie realmente necesita incluir para producir una respuesta precisa ... en lugar de requerir que peinemos manualmente los resultados de múltiples pruebas para descubrir eso.
Y, como ocurre con frecuencia en Raku, es sorprendentemente fácil construir justo lo que necesitamos:
sub Σ (Unary $block --> Numeric) {
(0..∞).map($block).produce(&[+]).&converge
}
Analizando tu código
Aquí está la primera línea, generando la serie:
my @e = 1, { state $a=1; 1 / ($_ * $a++) } ... *;
El cierre ( { code goes here }
) calcula un término. Un cierre tiene una firma, ya sea implícita o explícita, que determina cuántos argumentos aceptará. En este caso no hay firma explícita. El uso de $_
( la variable "tema" ) da como resultado una firma implícita que requiere un argumento vinculado $_
.
El operador de secuencia ( ...
) llama repetidamente al cierre a su izquierda, pasando el término anterior como argumento del cierre, para construir perezosamente una serie de términos hasta el punto final a su derecha, que en este caso es *
, abreviatura de Inf
aka infinito.
El tema en la primera llamada al cierre es 1
. Por lo tanto, el cierre calcula y devuelve 1 / (1 * 1)
los dos primeros términos de la serie como 1, 1/1
.
El tema en la segunda llamada es el valor de la anterior 1/1
, es decir, 1
nuevamente. Entonces el cierre calcula y regresa 1 / (1 * 2)
, extendiendo la serie a 1, 1/1, 1/2
. Todo se ve bien.
El próximo cierre calcula 1 / (1/2 * 3)
cuál es 0.666667
. Ese término debería ser 1 / (1 * 2 * 3)
. Ups
Hacer que su código coincida con la fórmula
Se supone que su código coincide con la fórmula:
En esta fórmula, cada término se calcula en función de su posición en la serie. El k ésimo término en la serie (donde k = 0 para la primera 1
) es sólo factorial k 's recíproco.
(Por lo tanto, no tiene nada que ver con el valor del término anterior. Por $_
lo tanto , el que recibe el valor del término anterior no debe usarse en el cierre).
Creemos un operador factorial de postfix:
sub postfix:<!> (\k) { [×] 1 .. k }
( ×
es un operador de multiplicación infijo, un alias Unicode más bonito del infijo ASCII habitual *
).
Esa es la abreviatura de:
sub postfix:<!> (\k) { 1 × 2 × 3 × .... × k }
(He usado la notación pseudo metasintáctica dentro de las llaves para denotar la idea de sumar o restar tantos términos como sea necesario.
Más generalmente, poner un operador infijo op
entre corchetes al comienzo de una expresión forma un operador de prefijo compuesto que es el equivalente de reduce with => &[op],
. Ver metaoperador de reducción para más información.
Ahora podemos reescribir el cierre para usar el nuevo operador factorial postfix:
my @e = 1, { state $a=1; 1 / $a++! } ... *;
Bingo. Esto produce la serie correcta.
... hasta que no lo haga, por una razón diferente. El siguiente problema es la precisión numérica. Pero tratemos con eso en la siguiente sección.
Una línea derivada de su código
Quizás comprimir las tres líneas a una:
say [+] .[^10] given 1, { 1 / [×] 1 .. ++$ } ... Inf
.[^10]
se aplica al tema, que establece el given
. ( ^10
es la abreviatura de 0..9
, por lo que el código anterior calcula la suma de los primeros diez términos de la serie).
He eliminado el $a
del cómputo del cierre del próximo trimestre Un solitario $
es lo mismo que (state $)
un estado anónimo escalar. Lo hice un incremento previo en lugar de incremento posterior para lograr el mismo efecto como lo hizo inicializando $a
a 1
.
Ahora nos queda el problema final (¡grande!), Que usted señaló en un comentario a continuación.
Siempre que ninguno de sus operandos sea un Num
(un flotante, y por lo tanto aproximado), el /
operador normalmente devuelve un 100% de precisión Rat
(una precisión limitada racional). Pero si el denominador del resultado excede los 64 bits, entonces ese resultado se convierte en a Num
, lo que cambia el rendimiento por precisión, una compensación que no queremos hacer. Necesitamos tener eso en cuenta.
Para especificar una precisión ilimitada y una precisión del 100%, simplemente coaccione la operación para usar FatRat
s. Para hacer esto correctamente, simplemente haga que (al menos) uno de los operandos sea a FatRat
(y ninguno sea a Num
):
say [+] .[^500] given 1, { 1.FatRat / [×] 1 .. ++$ } ... Inf
He verificado esto con 500 dígitos decimales. Espero que siga siendo preciso hasta que el programa se bloquee debido a que excede algún límite del lenguaje Raku o el compilador Rakudo. (Vea mi respuesta a Cannot unbox 65536 bit wideintint en entero nativo para una discusión sobre eso).
Notas al pie
1 Raku tiene unos importantes constantes matemáticas incorporadas, que incluyen e
, i
y pi
(y su alias π
). Por lo tanto, uno puede escribir la identidad de Euler en Raku, como se ve en los libros de matemáticas. Con crédito a la entrada de Raku de RosettaCode para la identidad de Euler :
# There's an invisible character between <> and iπ character pairs!
sub infix:<> (\left, \right) is tighter(&infix:<**>) { left * right };
# Raku doesn't have built in symbolic math so use approximate equal
say e**iπ + 1 ≅ 0; # True
2 El artículo de Damian es una lectura obligada. Pero es solo uno de varios tratamientos admirables que se encuentran entre las más de 100 coincidencias para un google para 'raku "número de euler" .
3 Vea TIMTOWTDI vs TSBO-APOO-OWTDI para obtener una de las vistas más equilibradas de TIMTOWTDI escritas por un fanático de Python. Pero no son inconvenientes para tomar TIMTOWTDI demasiado lejos. Para reflejar este último "peligro", la comunidad de Perl acuñó el TIMTOWTDIBSCINABTE humorísticamente largo, ilegible y discreto : hay más de una forma de hacerlo, pero a veces la consistencia no es algo malo tampoco, pronunció "Tim Toady Bicarbonate". Por extraño que parezca , Larry aplicó bicarbonato al diseño de Raku y Damian lo aplica a la informática e
en Raku.