Analizzo il tuo codice nella sezione Analizzare il tuo codice . Prima di allora vi presento un paio di sezioni divertenti di materiale bonus.
Una lettera Una lettera 1
say e; # 2.718281828459045
Fai clic sul link sopra per vedere l'articolo straordinario di Damian Conway e
sull'informatica a Raku.
L'articolo è molto divertente (dopo tutto, è Damian). È una discussione molto comprensibile sull'informatica e
. Ed è un omaggio alla reincarnazione bicarbonato di Raku della filosofia TIMTOWTDI promossa da Larry Wall. 3
Come antipasto, ecco una citazione da circa la metà dell'articolo:
Dato che questi metodi efficienti funzionano tutti allo stesso modo - sommando (un sottoinsieme iniziale di) una serie infinita di termini - forse sarebbe meglio se avessimo una funzione per farlo per noi. E sarebbe certamente meglio se la funzione potesse capire da sola esattamente la quantità di quel sottoinsieme iniziale della serie che deve effettivamente includere per produrre una risposta accurata ... piuttosto che richiederci di esaminare manualmente i risultati di prove multiple per scoprirlo.
E, come spesso accade a Raku, è sorprendentemente facile costruire esattamente ciò di cui abbiamo bisogno:
sub Σ (Unary $block --> Numeric) {
(0..∞).map($block).produce(&[+]).&converge
}
Analizzando il tuo codice
Ecco la prima riga, generando la serie:
my @e = 1, { state $a=1; 1 / ($_ * $a++) } ... *;
La chiusura ( { code goes here }
) calcola un termine. Una chiusura ha una firma, implicita o esplicita, che determina quanti argomenti accetterà. In questo caso non esiste una firma esplicita. L'uso della $_
( variabile "topic" ) genera una firma implicita che richiede un argomento a cui è associato $_
.
L'operatore sequenza ( ...
) chiama ripetutamente la chiusura alla sua sinistra, passando il termine precedente come argomento della chiusura, per costruire pigramente una serie di termini fino all'endpoint alla sua destra, che in questo caso è *
, abbreviazione di Inf
aka infinito.
L'argomento nella prima chiamata alla chiusura è 1
. Quindi la chiusura calcola e ritorna 1 / (1 * 1)
dando i primi due termini della serie come 1, 1/1
.
L'argomento nella seconda chiamata è il valore di quello precedente 1/1
, ovvero di 1
nuovo. Quindi la chiusura calcola e ritorna 1 / (1 * 2)
, estendendo la serie a 1, 1/1, 1/2
. Sembra tutto a posto.
La prossima chiusura calcola 1 / (1/2 * 3)
quale è 0.666667
. Quel termine dovrebbe essere 1 / (1 * 2 * 3)
. Ops.
Rendere il codice corrispondente alla formula
Il tuo codice dovrebbe corrispondere alla formula:
In questa formula, ogni termine viene calcolato in base alla sua posizione nella serie. Il k esimo termine della serie (dove k = 0 per il primo 1
) è semplicemente il reciproco di K fattoriale .
(Quindi non ha nulla a che fare con il valore del termine precedente. Pertanto $_
, che riceve il valore del termine precedente, non dovrebbe essere usato nella chiusura.)
Creiamo un operatore fattoriale postfix:
sub postfix:<!> (\k) { [×] 1 .. k }
( ×
è un operatore di moltiplicazione di infissi, un alias Unicode dall'aspetto più gradevole della solita infissione ASCII *
.)
Questa è una scorciatoia per:
sub postfix:<!> (\k) { 1 × 2 × 3 × .... × k }
(Ho usato la notazione pseudo metasintattica all'interno delle parentesi graffe per indicare l'idea di aggiungere o sottrarre tutti i termini richiesti.
Più in generale, mettere un operatore infisso op
tra parentesi quadre all'inizio di un'espressione costituisce un operatore prefisso composito che è l'equivalente di reduce with => &[op],
. Vedi Metaoperatore di riduzione per maggiori informazioni.
Ora possiamo riscrivere la chiusura per utilizzare il nuovo operatore fattoriale postfix:
my @e = 1, { state $a=1; 1 / $a++! } ... *;
Bingo. Questo produce le serie giuste.
... fino a quando non lo fa, per un motivo diverso. Il prossimo problema è l'accuratezza numerica. Ma affrontiamolo nella prossima sezione.
Una fodera derivata dal tuo codice
Forse comprimi le tre righe in una:
say [+] .[^10] given 1, { 1 / [×] 1 .. ++$ } ... Inf
.[^10]
si applica all'argomento, che è impostato da given
. ( ^10
è una scorciatoia per 0..9
, quindi il codice sopra riportato calcola la somma dei primi dieci termini della serie.)
Ho eliminato il $a
calcolo dalla chiusura il prossimo termine. Un solitario $
è lo stesso di (state $)
uno scalare anonimo di stato. Ho fatto un pre-incremento, invece di post-incremento per ottenere lo stesso effetto, come avete fatto per l'inizializzazione $a
a 1
.
Ora abbiamo lasciato il problema finale (grande!), Sottolineato da te in un commento qui sotto.
A condizione che nessuno dei suoi operandi sia un Num
(a virgola mobile, e quindi approssimativo), l' /
operatore restituisce normalmente una precisione del 100% Rat
(una precisione razionale limitata). Ma se il denominatore del risultato supera i 64 bit, quel risultato viene convertito in un Num
- che scambia le prestazioni per accuratezza, un compromesso che non vogliamo fare. Dobbiamo tenerne conto.
Per specificare una precisione illimitata e un'accuratezza del 100%, è sufficiente forzare l'operazione per usare FatRat
s. Per farlo correttamente, basta fare (almeno) uno degli operandi come un FatRat
(e nessun altro essere un Num
):
say [+] .[^500] given 1, { 1.FatRat / [×] 1 .. ++$ } ... Inf
Ho verificato questo con 500 cifre decimali. Mi aspetto che rimanga preciso fino a quando il programma non si arresta in modo anomalo a causa del superamento di alcuni limiti del linguaggio Raku o del compilatore Rakudo. (Vedi la mia risposta a Impossibile decomprimere bigint a 65536 bit in un intero nativo per alcune discussioni su questo.)
Le note
1 Raku ha alcuni importanti costanti matematiche integrate, tra cui e
, i
e pi
(e il suo alias π
). Così si può scrivere l'identità di Eulero in Raku un po 'come appare nei libri di matematica. Con credito alla voce Raku di RosettaCode per l'identità di 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'articolo di Damian è assolutamente da leggere. Ma è solo uno dei tanti trattamenti ammirevoli che sono tra le oltre 100 partite di un google per "raku" numero di eulero "' .
3 Vedi TIMTOWTDI vs TSBO-APOO-OWTDI per una delle viste più equilibrate di TIMTOWTDI scritta da un fan di Python. Ma ci sono aspetti negativi nel portare troppo TIMTOWTDI. Per riflettere quest'ultimo "pericolo", la comunità del Perl ha coniato il TIMTOWTDIBSCINABTE , ironicamente lungo, illeggibile e discreto - Esiste più di un modo per farlo, ma a volte la coerenza non è una cosa negativa, ha pronunciato "Tim Toady Bicarbonate". Stranamente , Larry ha applicato il bicarbonato al design di Raku e Damian lo applica al computing e
in Raku.