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
Kliknij powyższy link, aby zobaczyć niezwykły artykuł Damiana Conwaya na temat komputerów e
w 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:
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 op
przedrostka 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
. ( ^10
jest skrótem 0..9
od powyższego, więc powyższy kod oblicza sumę pierwszych dziesięciu terminów w serii).
Wyeliminowałem $a
z 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 $a
do1
.
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ć FatRat
s. 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
, i
oraz 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ń e
w Raku.