Calcular el número e usando Raku

9

Estoy tratando de calcular la constante e (también conocido como el número de Euler ) calculando la fórmula mi

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?

Lars Malmsteen
fuente
¿De qué manera lo hizo: "no funcionó"?
SherylHohman
1
La parte del denominador en el código de muestra no funcionó porque estaba haciendo uso de la variable de valor anterior $_ , en un intento de construir el factorial. Obviamente era redundante. En la solución correcta a continuación, $_se dejó caer y funcionó perfectamente.
Lars Malmsteen
Gracias. Supongo que estaba buscando más de lo que exactamente significaba esa declaración. Como si hubiera un error, cómo no fue consistente con lo que esperabas, ese tipo de cosas. Supongo que su cálculo no coincidió con las respuestas conocidas para ese cálculo. ¡Me alegra que lo hayas solucionado! Además, excelente descripción posterior a la respuesta sobre cuál era el problema real :-)
SherylHohman

Respuestas:

11

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

"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 een 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 Infaka 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, 1nuevamente. 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:
mi

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 opentre 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. ( ^10es 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 $adel 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 $aa 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 FatRats. 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, iy 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 een Raku.

raiph
fuente
Gracias por la respuesta. La sección titulada My way based on your way lo resuelve bastante bien. Sin embargo, necesito buscar las complejidades. No sabía que una llanura $era una abreviatura state $, es bastante útil.
Lars Malmsteen
¿Hay alguna manera de especificar el número de dígitos de ela 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ó.
Lars Malmsteen
@LarsMalmsteen He abordado su FatRatpregunta muy importante en la última sección. También he perfeccionado toda la respuesta, aunque el único cambio importante es el FatRatmaterial. (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).
Raiph
Gracias por el esfuerzo extra. Por lo tanto, la .FatRatextensión debe colocarse dentro del generador de código. Ahora lo intenté con FatRatagregado 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 que sayestaba truncando las largas matrices / secuencias. Tales bits de información son buenos para saber.
Lars Malmsteen
@LarsMalmsteen :) "Entonces, la .FatRatextensió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 desborda Ratla precisión. Si es así, se evaluará a un Num(flotante) y eso a su vez contamina cualquier cálculo adicional que lo involucre, haciéndolos también Num . La única manera de asegurar que las cosas estancia FatRates para empezar ellos FatRaty evitar cualquier Nums. Intsys Ratestán bien, siempre que haya al menos uno FatRatpara que Raku sepa que debe seguir con FatRats.
raiph
9

Hay fracciones en $_. Por lo tanto, necesita 1 / (1/$_ * $a++)o más bien $_ /$a++.

Por Raku podrías hacer este cálculo paso a paso

1.FatRat,1,2,3 ... *   #1 1 2 3 4 5 6 7 8 9 ...
andthen .produce: &[*] #1 1 2 6 24 120 720 5040 40320 362880
andthen .map: 1/*      #1 1 1/2 1/6 1/24 1/120 1/720 1/5040 1/40320 1/362880 ...
andthen .produce: &[+] #1 2 2.5 2.666667 2.708333 2.716667 2.718056 2.718254 2.718279 2.718282 ...
andthen .[50].say      #2.71828182845904523536028747135266249775724709369995957496696762772
wamba
fuente
Agradable. No tenía ni idea de andthen.
Holli