Estoy tratando de calcular la constante e (también conocido como el número de Euler ) calculando la fórmula
Para calcular el factorial y la división en una sola toma, escribí esto:
my @e = 1, { state $a=1; 1 / ($_ * $a++) } ... *;
say reduce * + * , @e[^10];
Pero no funcionó. ¿Cómo hacerlo correctamente?
code-generation
lazy-evaluation
raku
eulers-number
Lars Malmsteen
fuente
fuente
$_
, en un intento de construir el factorial. Obviamente era redundante. En la solución correcta a continuación,$_
se dejó caer y funcionó perfectamente.Respuestas:
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 linerOne letter 1"Un tratado sobre múltiples formas" 2
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. 3Como aperitivo, aquí hay una cita de aproximadamente la mitad del artículo:
Analizando tu código
Aquí está la primera línea, generando la serie:
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 deInf
aka infinito.El tema en la primera llamada al cierre es
1
. Por lo tanto, el cierre calcula y devuelve1 / (1 * 1)
los dos primeros términos de la serie como1, 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 regresa1 / (1 * 2)
, extendiendo la serie a1, 1/1, 1/2
. Todo se ve bien.El próximo cierre calcula
1 / (1/2 * 3)
cuál es0.666667
. Ese término debería ser1 / (1 * 2 * 3)
. UpsHacer 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:
(
×
es un operador de multiplicación infijo, un alias Unicode más bonito del infijo ASCII habitual*
).Esa es la abreviatura de:
(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 dereduce with => &[op],
. Ver metaoperador de reducción para más información.Ahora podemos reescribir el cierre para usar el nuevo operador factorial postfix:
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:
.[^10]
se aplica al tema, que establece elgiven
. (^10
es la abreviatura de0..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
a1
.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ónRat
(una precisión limitada racional). Pero si el denominador del resultado excede los 64 bits, entonces ese resultado se convierte en aNum
, 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 aFatRat
(y ninguno sea aNum
):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
ypi
(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 :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.fuente
$
era una abreviaturastate $
, es bastante útil.e
la tercera solución (titulada Mi camino según tu camino )? Intenté agregar FatRat (500) al lado de 1 en:... given 1.FatRat(500), ...
para que los números tengan una precisión de 500 dígitos, pero no funcionó.FatRat
pregunta muy importante en la última sección. También he perfeccionado toda la respuesta, aunque el único cambio importante es elFatRat
material. (Por cierto, me doy cuenta de que gran parte de mi respuesta es realmente tangencial a su pregunta original; confío en que no le importó que escribiera toda la pelusa adicional para entretenerme y quizás ser interesante para los lectores posteriores)..FatRat
extensión debe colocarse dentro del generador de código. Ahora lo intenté conFatRat
agregado de esta manera y calculó la e con la precisión de más de 1000 dígitos. La pelusa adicional agregada es útil. Por ejemplo, no sabía quesay
estaba truncando las largas matrices / secuencias. Tales bits de información son buenos para saber..FatRat
extensión debe colocarse dentro del generador de código". Si. En términos más generales, si una expresión que involucra división ya ha sido evaluada, es demasiado tarde para deshacer el daño causado si desbordaRat
la precisión. Si es así, se evaluará a unNum
(flotante) y eso a su vez contamina cualquier cálculo adicional que lo involucre, haciéndolos tambiénNum
. La única manera de asegurar que las cosas estanciaFatRat
es para empezar ellosFatRat
y evitar cualquierNum
s.Int
sysRat
están bien, siempre que haya al menos unoFatRat
para que Raku sepa que debe seguir conFatRat
s.Hay fracciones en
$_
. Por lo tanto, necesita1 / (1/$_ * $a++)
o más bien$_ /$a++
.Por Raku podrías hacer este cálculo paso a paso
fuente
andthen
.