Calcul du nombre e à l'aide de Raku

9

J'essaie de calculer la constante e ( AKA Euler's Number ) en calculant la formule e

Afin de calculer la factorielle et la division en un seul coup, j'ai écrit ceci:

my @e = 1, { state $a=1; 1 / ($_ * $a++) } ... *;
say reduce  * + * , @e[^10];

Mais ça n'a pas marché. Comment le faire correctement?

Lars Malmsteen
la source
De quelle manière: "ça n'a pas marché"?
SherylHohman
1
La partie dénominateur dans l'exemple de code n'a pas fonctionné car elle utilisait la variable de valeur précédente $_ , pour tenter de construire la factorielle. C'était évidemment redondant. Dans la bonne solution ci-dessous, a $_été supprimé et cela a parfaitement fonctionné.
Lars Malmsteen
Merci. Je suppose que je cherchais davantage ce que signifiait exactement cette déclaration. Comme s'il y avait une erreur, comment n'était pas cohérent avec ce que vous attendiez, ce genre de chose. Je suppose que votre calcul ne correspond pas aux réponses connues pour ce calcul. Heureux que vous ayez réussi !! En outre, une excellente description post-réponse de quel était le problème réel :-)
SherylHohman

Réponses:

11

J'analyse votre code dans la section Analyser votre code . Avant cela, je présente quelques sections amusantes de matériel bonus.

Une doublure Une lettre 1

say e; # 2.718281828459045

"Un traité aux multiples voies" 2

Cliquez sur le lien ci-dessus pour voir l'article extraordinaire de Damian Conway sur l'informatique eà Raku.

L'article est très amusant (après tout, c'est Damian). C'est une discussion très compréhensible de l'informatique e. Et c'est un hommage à la réincarnation par Raku du bicarbonate de la philosophie TIMTOWTDI adoptée par Larry Wall. 3

En entrée, voici une citation d'environ la moitié de l'article:

Étant donné que ces méthodes efficaces fonctionnent toutes de la même manière - en sommant (un sous-ensemble initial de) une série infinie de termes - il serait peut-être préférable que nous ayons une fonction pour le faire pour nous. Et il serait certainement préférable que la fonction puisse déterminer par elle-même exactement la quantité de ce sous-ensemble initial de la série qu'elle doit réellement inclure afin de produire une réponse précise ... plutôt que de nous obliger à peigner manuellement les résultats de plusieurs essais pour découvrir cela.

Et, comme souvent à Raku, il est étonnamment facile de construire exactement ce dont nous avons besoin:

sub Σ (Unary $block --> Numeric) {
  (0..∞).map($block).produce(&[+]).&converge
}

Analyser votre code

Voici la première ligne, générant la série:

my @e = 1, { state $a=1; 1 / ($_ * $a++) } ... *;

La fermeture ( { code goes here }) calcule un terme. Une fermeture a une signature, implicite ou explicite, qui détermine le nombre d'arguments qu'elle acceptera. Dans ce cas, il n'y a pas de signature explicite. L'utilisation de $_( la variable "topic" ) entraîne une signature implicite qui nécessite un argument lié à $_.

L'opérateur de séquence ( ...) appelle à plusieurs reprises la fermeture sur sa gauche, en passant le terme précédent comme argument de fermeture, pour construire paresseusement une série de termes jusqu'à l'extrémité à sa droite, qui dans ce cas est un *raccourci pour Infaka infini.

Le sujet du premier appel à la clôture est 1. La fermeture calcule et renvoie donc 1 / (1 * 1)les deux premiers termes de la série as 1, 1/1.

Le sujet du deuxième appel est la valeur du précédent 1/1, c'est- 1à- dire à nouveau. Ainsi, la fermeture calcule et retourne 1 / (1 * 2), étendant la série à 1, 1/1, 1/2. Tout a l'air bien.

La prochaine fermeture calcule 1 / (1/2 * 3)ce qui est 0.666667. Ce terme devrait être 1 / (1 * 2 * 3). Oops.

Faire correspondre votre code à la formule

Votre code est censé correspondre à la formule:
e

Dans cette formule, chaque terme est calculé en fonction de sa position dans la série. Le k ème terme de la série (où k = 0 pour le premier 1) est juste réciproque factoriel k .

(Donc, cela n'a rien à voir avec la valeur du terme précédent. Ainsi $_, qui reçoit la valeur du terme précédent, ne doit pas être utilisé dans la fermeture.)

Créons un opérateur de suffixe factoriel:

sub postfix:<!> (\k) { [×] 1 .. k }

( ×est un opérateur de multiplication d'infixes, un alias Unicode plus joli de l'infixe ASCII habituel *.)

C'est un raccourci pour:

sub postfix:<!> (\k) { 1 × 2 × 3 × .... × k }

(J'ai utilisé une notation pseudo métasyntaxique à l'intérieur des accolades pour indiquer l'idée d'ajouter ou de soustraire autant de termes que nécessaire.

Plus généralement, mettre un opérateur infixe opentre crochets au début d'une expression forme un opérateur préfixe composite qui est l'équivalent de reduce with => &[op],. Voir Méta-opérateur de réduction pour plus d'informations.

Nous pouvons maintenant réécrire la fermeture pour utiliser le nouvel opérateur factoriel postfix:

my @e = 1, { state $a=1; 1 / $a++! } ... *;

Bingo. Cela produit la bonne série.

... jusqu'à ce que ce ne soit pas le cas, pour une raison différente. Le problème suivant est la précision numérique. Mais examinons cela dans la section suivante.

Un liner dérivé de votre code

Peut-être compresser les trois lignes en une seule:

say [+] .[^10] given 1, { 1 / [×] 1 .. ++$ } ... Inf

.[^10]s'applique au sujet, qui est défini par le given. ( ^10est un raccourci pour 0..9, donc le code ci-dessus calcule la somme des dix premiers termes de la série.)

J'ai éliminé $ade la fermeture le calcul du prochain mandat. Un solitaire $est identique à (state $)un scalaire à état anonynique. J'ai fait un pré-incrément au lieu d'un post-incrément pour obtenir le même effet que vous avez fait en l'initialisant $aà 1.

Il nous reste maintenant le dernier (gros!) Problème, que vous avez signalé dans un commentaire ci-dessous.

À condition qu'aucun de ses opérandes ne soit un Num(un flottant, et donc approximatif), l' /opérateur retourne normalement un 100% précis Rat(une précision limitée rationnelle). Mais si le dénominateur du résultat dépasse 64 bits, ce résultat est converti en un Num- qui échange les performances contre la précision, un compromis que nous ne voulons pas faire. Nous devons en tenir compte.

Pour spécifier une précision illimitée ainsi qu'une précision de 100%, il suffit de contraindre l'opération à utiliser l' FatRatart. Pour le faire correctement, il suffit de faire (au moins) l'un des opérandes être un FatRat(et aucun autre être un Num):

say [+] .[^500] given 1, { 1.FatRat / [×] 1 .. ++$ } ... Inf

J'ai vérifié cela à 500 chiffres décimaux. Je m'attends à ce qu'il reste précis jusqu'à ce que le programme plante en raison du dépassement d'une limite de la langue Raku ou du compilateur Rakudo. (Voir ma réponse à Cannot unbox 65536 bit large bigint into native integer for some discussion of that.)

Notes de bas de page

1 Raku a quelques constantes mathématiques importantes intégrées, y compris e, iet pi(et son alias π). Ainsi, on peut écrire l'identité d'Euler dans Raku un peu comme on dirait dans les livres de mathématiques. Avec crédit à l'entrée Raku de RosettaCode pour l'identité d'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 L'article de Damian est à lire absolument. Mais ce n'est qu'un des nombreux traitements admirables qui font partie des 100+ correspondances pour un google pour le "raku" euler's number "' .

3 Voir TIMTOWTDI vs TSBO-APOO-OWTDI pour l'une des vues les plus équilibrées de TIMTOWTDI écrites par un fan de python. Mais il y a des inconvénients à aller trop loin avec TIMTOWTDI. Pour refléter ce dernier "danger", la communauté Perl a inventé TIMTOWTDIBSCINABTE , humoristique, long et illisible - Il y a plus d'une façon de le faire, mais parfois la cohérence n'est pas une mauvaise chose non plus, prononcé "Tim Toady Bicarbonate". Curieusement , Larry a appliqué du bicarbonate au design de Raku et Damian l'applique à l'informatique edans Raku.

raiph
la source
Merci pour la réponse. La section intitulée Mon chemin basé sur votre chemin le résout assez bien. Je dois cependant rechercher les subtilités. Je ne savais pas qu'une plaine $était un raccourci state $, c'est assez pratique.
Lars Malmsteen
Existe-t-il un moyen de spécifier le nombre de chiffres de epour la 3e solution (intitulé Mon chemin en fonction de votre chemin )? J'ai essayé d'ajouter FatRat (500) à côté de 1 po: ... given 1.FatRat(500), ...pour que les chiffres soient précis à 500 chiffres, mais cela n'a pas fonctionné.
Lars Malmsteen
@LarsMalmsteen J'ai répondu à votre FatRatquestion très importante dans la dernière section. J'ai également affiné toute la réponse, bien que le seul changement majeur soit la FatRatsubstance. (Btw, je me rends compte qu'une grande partie de ma réponse est vraiment tangentielle à votre question d'origine; j'espère que cela ne vous a pas dérangé d'écrire tous les trucs supplémentaires pour me divertir et peut-être être intéressant pour les lecteurs ultérieurs.)
Raiph
Merci pour l'effort supplémentaire. L' .FatRatextension doit donc être placée à l'intérieur du générateur de code. Maintenant, je l'ai essayé avec l' FatRatajout de cette façon et il a calculé le e à la précision de 1000+ chiffres. Les peluches supplémentaires ajoutées sont inutiles. Par exemple, je ne savais pas que le saytronçonnait les longs tableaux / séquences. Ces informations sont bonnes à savoir.
Lars Malmsteen
@LarsMalmsteen :) "Donc l' .FatRatextension doit être placée à l'intérieur du générateur de code.". Oui. Plus généralement, si une expression impliquant une division a déjà été évaluée, il est trop tard pour réparer les dommages causés si elle dépassait la Ratprécision. Si c'est le cas, il évaluera à un Num(flottant) et cela à son tour entacher tous les calculs supplémentaires impliquant, en les faisant aussi Num . La seule façon de garantir un séjour des choses FatRatest de commencer les FatRatet éviter tout Nums. Ints et Rats sont OK, à condition qu'il y en ait au moins un FatRatpour que Raku sache s'en tenir à FatRats.
raiph
9

Il y a des fractions dedans $_. Ainsi vous avez besoin 1 / (1/$_ * $a++)ou plutôt $_ /$a++.

Par Raku, vous pouvez faire ce calcul étape par étape

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
la source
Agréable. Je n'en avais aucune idée andthen.
Holli