Obliczanie liczby e za pomocą Raku

9

Próbuję obliczyć stałą e ( AKA Euler's Number ), obliczając wzór mi

Aby obliczyć silnię i podział w jednym ujęciu, napisałem to:

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

Ale to nie wyszło. Jak to zrobić poprawnie?

Lars Malmsteen
źródło
W jaki sposób: „nie wyszło”?
SherylHohman
1
Część mianownika w przykładowym kodzie nie zadziałała, ponieważ wykorzystywała poprzednią $_ zmienną wartości , próbując skonstruować silnię. To było oczywiście zbędne. W poprawnym rozwiązaniu poniżej $_został upuszczony i działał idealnie.
Lars Malmsteen,
Dzięki. Wydaje mi się, że szukałem więcej tego, co dokładnie oznaczało to stwierdzenie. Jakby był błąd, jak nie był zgodny z tym, czego się spodziewałeś, tego typu rzeczy. Myślę, że twoje obliczenia nie pasują do znanych odpowiedzi dla tego obliczenia. Cieszę się, że to się udało !! Również świetny opis po odpowiedzi, jaki był faktyczny problem :-)
SherylHohman

Odpowiedzi:

11

Analizuję twój kod w sekcji Analiza twojego kodu . Wcześniej przedstawiam kilka zabawnych sekcji materiałów bonusowych.

Jedna wkładka Jedna litera 1

say e; # 2.718281828459045

„Traktat na wiele sposobów” 2

Kliknij powyższy link, aby zobaczyć niezwykły artykuł Damiana Conwaya na temat komputerów ew Raku.

Artykuł jest świetną zabawą (w końcu to Damian). To bardzo zrozumiała dyskusja na temat komputerów e. I jest hołdem dla wodorowęglanowej reinkarnacji Raku w filozofii TIMTOWTDI, popieranej przez Larry'ego Walla. 3)

Jako przystawkę, oto cytat z około połowy artykułu:

Biorąc pod uwagę, że wszystkie te wydajne metody działają w ten sam sposób - sumując (początkowy podzbiór) nieskończonej serii terminów - być może byłoby lepiej, gdybyśmy mieli taką funkcję dla siebie. I na pewno byłoby lepiej, gdyby funkcja mogła sama ustalić, ile początkowych podzbiorów serii musi faktycznie zawierać, aby uzyskać dokładną odpowiedź ... zamiast wymagać od nas ręcznego przeczesywania wyników wiele prób, aby to odkryć.

I, jak to często bywa w Raku, zaskakująco łatwo jest zbudować dokładnie to, czego potrzebujemy:

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

Analizując twój kod

Oto pierwsza linia, generująca serię:

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

Funkcja closure ( { code goes here }) oblicza termin. Zamknięcie ma podpis, jawny lub jawny, który określa, ile argumentów zostanie zaakceptowanych. W tym przypadku nie ma wyraźnego podpisu. Użycie $_( zmiennej „topic” ) skutkuje niejawną sygnaturą, która wymaga jednego związanego z nią argumentu$_ .

Operator sekwencji ( ...) wielokrotnie wywołuje zamknięcie po lewej stronie, mijając poprzedni termin jako argument zamknięcia, aby leniwie zbudować serię terminów aż do punktu końcowego po prawej stronie, który w tym przypadku jest *skrótemInf aka nieskończoności.

Tematem pierwszego wezwania do zamknięcia jest 1. Zatem zamknięcie oblicza i zwraca, 1 / (1 * 1)uzyskując pierwsze dwa warunki w szeregu jako1, 1/1 .

Tematem drugiego wezwania jest wartość poprzedniego 1/1, tj 1. Ponownie. Więc zamknięcie oblicza i zwraca 1 / (1 * 2), rozszerzając serię na 1, 1/1, 1/2. Wszystko wygląda dobrze.

Następne zamknięcie oblicza, 1 / (1/2 * 3)co jest 0.666667. Ten termin powinien być 1 / (1 * 2 * 3). Ups

Dopasowanie kodu do formuły

Twój kod powinien pasować do wzoru:
mi

W tym wzorze każdy termin jest obliczany na podstawie jego pozycji w szeregu. K p terminu w serii (gdzie k = 0, w pierwszym 1) jest tak silnia K jest wzajemne.

(Więc nie ma to nic wspólnego z wartością wcześniejszego terminu. Dlatego też $_, która otrzymuje wartość z poprzedniego terminu, nie powinna być używana w zamknięciu).

Utwórzmy czynnikowego operatora Postfiksa:

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

( ×jest operatorem mnożenia infekcji, ładniejszym aliasem Unicode zwykłej infiksu ASCII* .)

To jest skrót od:

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

(Użyłem pseudometazntaktycznej notacji w nawiasach klamrowych, aby wskazać pomysł dodania lub odjęcia dowolnej liczby terminów, zgodnie z wymaganiami.

Mówiąc bardziej ogólnie, umieszczenie operatora opprzedrostka w nawiasach kwadratowych na początku wyrażenia tworzy złożony operator przedrostka, który jest równoważny reduce with => &[op],. Zobacz Metaoperator redukcji więcej informacji, .

Teraz możemy przepisać zamknięcie, aby użyć nowego operatora czynnikowego porostku:

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

Bingo To daje właściwą serię.

... dopóki tak się nie stanie, z innego powodu. Kolejnym problemem jest dokładność numeryczna. Ale zajmijmy się tym w następnym rozdziale.

Jedna linijka wyprowadzona z twojego kodu

Może skompresuj trzy linie do jednego:

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

.[^10]dotyczy tematu ustawionego przez given. ( ^10jest skrótem 0..9od powyższego, więc powyższy kod oblicza sumę pierwszych dziesięciu terminów w serii).

Wyeliminowałem $az obliczeń zamknięcia następnego terminu. Samotny $to taki sam, jak (state $)anonimowy skalar stanu. Zrobiłem mu preinkrementuj zamiast post-przyrostu, aby osiągnąć ten sam efekt, jak to było przez inicjowanie $ado1 .

Pozostaje nam ostatni problem (duży!), O którym wspomniałeś w komentarzu poniżej.

Pod warunkiem, że żaden z jego argumentów nie jest Num(liczba zmiennoprzecinkowa, a zatem przybliżona), /operator zwykle zwraca 100% dokładności Rat(racjonalna ograniczona precyzja). Ale jeśli mianownik wyniku przekracza 64 bity, wynik ten jest konwertowany na Num- co wymienia wydajność pod względem dokładności - kompromis, którego nie chcemy robić. Musimy to wziąć pod uwagę.

Aby określić nieograniczoną precyzję, a także 100% dokładności, po prostu wymusz operację, aby użyć FatRats. Aby zrobić to poprawnie, po prostu ustaw (przynajmniej) jeden z operandów na a FatRat(i żaden inny nie będzie Num):

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

Zweryfikowałem to do 500 cyfr dziesiętnych. Oczekuję, że pozostanie dokładny, dopóki program się nie zawiesi z powodu przekroczenia pewnego limitu języka Raku lub kompilatora Rakudo. (Zobacz moją odpowiedź na Nie można rozpakować biginta o szerokości 65536 bitów na natywną liczbę całkowitą, aby o tym porozmawiać.)

Przypisy

1 Raku ma kilka ważnych stałych matematycznych wbudowanych w tym e, ioraz pi(i jego pseudonim π). W ten sposób można napisać Tożsamość Eulera w Raku, podobnie jak w książkach matematycznych. Z kredytu do wpisu Raku RosettaCode za Tożsamość Eulera :

# 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 Artykuł Damiana należy przeczytać. Ale to tylko jeden z kilku godnych podziwu zabiegów, które są wśród ponad 100 dopasowań dla google dla „raku” numeru eulera ” .

3 Zobacz TIMTOWTDI vs TSBO-APOO-OWTDI, aby zapoznać się z jednym z bardziej zrównoważonych widoków TIMTOWTDI napisanych przez fanów Pythona. Ale są też wady zbyt daleko idącego TIMTOWTDI. Aby odzwierciedlić to ostatnie „niebezpieczeństwo”, społeczność Perla wymyśliła humorystycznie długi, nieczytelny i zaniżony TIMTOWTDIBSCINABTE - Istnieje więcej niż jeden sposób, ale czasami spójność nie jest złą rzeczą, wymawianą jako „Tim Toady Biwęglan”. O dziwo , Larry zastosował wodorowęglan do projektu Raku, a Damian zastosował go do obliczeń ew Raku.

raiph
źródło
Dziękuję za Twoją odpowiedź. Część zatytułowana „ Moja droga na podstawie twojej drogi” rozwiązuje to całkiem dobrze. Muszę jednak sprawdzić zawiłości. Nie wiedziałem, że równina $jest skrótem state $, jest całkiem przydatna.
Lars Malmsteen,
Czy istnieje sposób na określenie liczby cyfr edla trzeciego rozwiązania (zatytułowanego Moja droga na podstawie Twojej drogi )? Próbowałem dodać FatRat (500) obok 1 w: ... given 1.FatRat(500), ...aby liczby były 500- cyfrowe , ale to nie zadziałało.
Lars Malmsteen,
@ LarsMalmsteen W FatRatostatnim rozdziale odniosłem się do twojego bardzo ważnego pytania. Dopracowałem też całą odpowiedź, chociaż jedyną poważną zmianą są FatRatrzeczy. (Przy okazji, zdaję sobie sprawę, że duża część mojej odpowiedzi jest naprawdę styczna do twojego pierwotnego pytania; ufam, że nie masz nic przeciwko, że napisałem cały dodatkowy puch, aby się bawić i być może być interesujący dla późniejszych czytelników.)
raiph
Dziękuję za dodatkowy wysiłek. Dlatego .FatRatrozszerzenie należy umieścić w generatorze kodu. Teraz wypróbowałem to z FatRatdodanym w ten sposób i obliczyło e z dokładnością do 1000 cyfr. Dodatkowy puch dodany jest w tym czasie. Na przykład nie wiedziałem, że sayskraca długie tablice / sekwencje. Takie fragmenty informacji są dobrze znane.
Lars Malmsteen,
@ LarsMalmsteen :) „Więc .FatRatrozszerzenie musi być umieszczone w generatorze kodu.”. Tak. Mówiąc bardziej ogólnie, jeśli wyrażenie obejmujące podział zostało już ocenione, jest już za późno, aby cofnąć uszkodzenia spowodowane przekroczeniem Ratprecyzji. Jeśli tak, będzie oceniać na Num(zmiennoprzecinkowe), a to z kolei zabezpieczy wszelkie dalsze obliczenia z tym związane, czyniąc je również Num . Jedynym sposobem, aby upewnić się, że rzeczy pozostaną, FatRatjest ich uruchomienieFatRat i uniknięcie jakichkolwiek Nums. Ints i Rats są w porządku, pod warunkiem, że jest co najmniej jeden, FatRatktóry pozwoli Raku wiedzieć, aby się trzymał FatRat.
raiph
9

Jest ułamek w $_. Zatem potrzebujesz, 1 / (1/$_ * $a++)a raczej$_ /$a++ .

Przez Raku możesz wykonać te obliczenia krok po kroku

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
źródło
Miły. Nie miałem pojęcia andthen.
Holli