Analiso seu código na seção Analisando seu código . Antes disso, apresento algumas seções divertidas de material bônus.
Um forro Uma letra 1
say e; # 2.718281828459045
Clique no link acima para ver o extraordinário artigo de Damian Conway sobre computação e
em Raku.
O artigo é muito divertido (afinal, é Damian). É uma discussão muito compreensível sobre computação e
. E é uma homenagem à reencarnação de bicarbonato de Raku da filosofia TIMTOWTDI adotada por Larry Wall. 3
Como aperitivo, aqui está uma citação de aproximadamente na metade do artigo:
Dado que todos esses métodos eficientes funcionam da mesma maneira - somando (um subconjunto inicial de) uma série infinita de termos - talvez fosse melhor se tivéssemos a função de fazer isso por nós. E certamente seria melhor se a função pudesse descobrir por si mesma exatamente quanto desse subconjunto inicial da série realmente precisa incluir para produzir uma resposta precisa ... em vez de exigir que analisemos manualmente os resultados de várias tentativas para descobrir isso.
E, como tantas vezes em Raku, é surpreendentemente fácil criar exatamente o que precisamos:
sub Σ (Unary $block --> Numeric) {
(0..∞).map($block).produce(&[+]).&converge
}
Analisando seu código
Aqui está a primeira linha, gerando a série:
my @e = 1, { state $a=1; 1 / ($_ * $a++) } ... *;
O encerramento ( { code goes here }
) calcula um termo. Um fechamento tem uma assinatura, implícita ou explícita, que determina quantos argumentos ele aceitará. Nesse caso, não há assinatura explícita. O uso de $_
( a variável "topic" ) resulta em uma assinatura implícita que requer um argumento ao qual está vinculado $_
.
O operador de sequência ( ...
) chama repetidamente o fechamento à esquerda, passando o termo anterior como argumento do fechamento, para construir preguiçosamente uma série de termos até o ponto final à direita, que neste caso é uma *
abreviação de Inf
aka infinito.
O tópico na primeira chamada para o encerramento é 1
. Portanto, o fechamento calcula e retorna 1 / (1 * 1)
produzindo os dois primeiros termos da série como 1, 1/1
.
O tópico na segunda chamada é o valor da anterior 1/1
, ou seja, 1
novamente. Portanto, o fechamento calcula e retorna 1 / (1 * 2)
, estendendo a série para 1, 1/1, 1/2
. Tudo parece bom.
O próximo fechamento calcula 1 / (1/2 * 3)
qual é 0.666667
. Esse termo deveria ser 1 / (1 * 2 * 3)
. Opa
Fazendo seu código corresponder à fórmula
Seu código deve corresponder à fórmula:
Nesta fórmula, cada termo é calculado com base em sua posição na série. O k ésimo termo da série (onde k = 0 para o primeiro 1
) é apenas o fatorial k 'recíproco.
(Portanto, não tem nada a ver com o valor do termo anterior. Portanto $_
, que recebe o valor do termo anterior, não deve ser usado no fechamento.)
Vamos criar um operador postfix fatorial:
sub postfix:<!> (\k) { [×] 1 .. k }
( ×
é um operador de multiplicação de infixos, um alias Unicode de aparência mais agradável do infixo ASCII usual *
.)
Isso é uma abreviação para:
sub postfix:<!> (\k) { 1 × 2 × 3 × .... × k }
(Usei notação pseudo-metassintática dentro do aparelho para denotar a ideia de adicionar ou subtrair tantos termos quanto necessário.
De maneira mais geral, colocar um operador infix op
entre colchetes no início de uma expressão forma um operador de prefixo composto equivalente a reduce with => &[op],
. Consulte Metaoperador de redução para obter mais informações.
Agora podemos reescrever o fechamento para usar o novo operador de postfix fatorial:
my @e = 1, { state $a=1; 1 / $a++! } ... *;
Bingo. Isso produz a série certa.
... até que não aconteça, por um motivo diferente. O próximo problema é a precisão numérica. Mas vamos tratar disso na próxima seção.
Um liner derivado do seu código
Talvez comprima as três linhas em uma:
say [+] .[^10] given 1, { 1 / [×] 1 .. ++$ } ... Inf
.[^10]
aplica-se ao tópico, definido pelo given
. ( ^10
é uma abreviação de 0..9
, portanto, o código acima calcula a soma dos dez primeiros termos da série.)
Eu eliminei $a
o fechamento do computador no próximo período. Um solitário $
é o mesmo que (state $)
um escalar anônimo de estado. Fiz-lhe um pré-incremento em vez de pós-incremento para alcançar o mesmo efeito que você fez por inicializar $a
a 1
.
Agora ficamos com o problema final (grande!), Apontado por você em um comentário abaixo.
Desde que nenhum de seus operandos seja um Num
(um flutuador e, portanto, aproximado), o /
operador normalmente retorna 100% de precisão Rat
(um racional de precisão limitado). Mas se o denominador do resultado exceder 64 bits, esse resultado será convertido em um Num
- que troca desempenho por precisão, uma troca que não queremos fazer. Precisamos levar isso em conta.
Para especificar precisão ilimitada e precisão de 100%, basta coagir a operação a usar FatRat
s. Para fazer isso corretamente, basta tornar (pelo menos) um dos operandos um FatRat
(e nenhum outro ser um Num
):
say [+] .[^500] given 1, { 1.FatRat / [×] 1 .. ++$ } ... Inf
Eu verifiquei isso com 500 dígitos decimais. Espero que ele permaneça preciso até o programa travar devido a exceder algum limite da linguagem Raku ou do compilador Rakudo. (Veja minha resposta para Não é possível desmarcar bigint de 65536 bits de largura em número inteiro nativo para alguma discussão sobre isso.)
Notas de rodapé
1 Raku tem algumas importantes constantes matemáticas construídas em, inclusive e
, i
e pi
(e seu alias π
). Assim, pode-se escrever a identidade de Euler em Raku, algo como nos livros de matemática. Com crédito à entrada de Raku da RosettaCode para a Identidade 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 O artigo de Damian é uma leitura obrigatória. Mas é apenas um dos vários tratamentos admiráveis que estão entre as mais de 100 correspondências de um google para 'raku "número de euler"' .
3 Consulte TIMTOWTDI vs TSBO-APOO-OWTDI para obter uma das visualizações mais equilibradas do TIMTOWTDI escritas por um fã de python. Mas não são desvantagens para tomar TIMTOWTDI longe demais. Para refletir esse último "perigo", a comunidade Perl cunhou o TIMTOWTDIBSCINABTE humoristicamente longo, ilegível e discreto - há mais de uma maneira de fazer isso, mas às vezes a consistência também não é uma coisa ruim, pronuncia-se "Tim Toady Bicarbonato". Estranhamente , Larry aplicou bicarbonato no design de Raku e Damian o aplica à computação e
em Raku.