Cálculo do número e usando Raku

9

Estou tentando calcular a constante e (número do AKA Euler ) calculando a fórmula e

Para calcular o fatorial e a divisão de uma só vez, escrevi o seguinte:

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

Mas não deu certo. Como fazer isso corretamente?

Lars Malmsteen
fonte
De que maneira: "não deu certo"?
SherylHohman 31/01
11
A parte do denominador no código de amostra não deu certo porque estava usando a variável de valor anterior $_ , na tentativa de construir o fatorial. Era obviamente redundante. Na solução correta abaixo, $_foi descartado e funcionou perfeitamente.
Lars Malmsteen
Obrigado. Acho que estava procurando mais o que exatamente significava essa afirmação. Como houve um erro, como não era consistente com o que você esperava, esse tipo de coisa. Acho que seu cálculo não corresponde às respostas conhecidas para esse cálculo. Feliz que você tenha resolvido isso !! Além disso, uma excelente descrição pós-resposta sobre qual era o problema real :-)
SherylHohman

Respostas:

11

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

"Um tratado de várias maneiras" 2

Clique no link acima para ver o extraordinário artigo de Damian Conway sobre computação eem 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 Infaka 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, 1novamente. 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:
e

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 opentre 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 $ao 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 $aa 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 FatRats. 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, ie 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 eem Raku.

raiph
fonte
Obrigado pela resposta. A seção intitulada Meu caminho, com base no seu caminho, resolve isso muito bem. Eu preciso procurar os meandros embora. Eu não sabia que uma planície $era uma abreviação para state $, é bastante útil.
Lars Malmsteen
Existe uma maneira de especificar o número de dígitos da e3ª solução (intitulado Meu caminho com base no seu caminho )? Tentei adicionar o FatRat (500) ao lado de 1 em: ... given 1.FatRat(500), ...para tornar os números com precisão de 500 dígitos, mas não funcionou.
Lars Malmsteen
@LarsMalmsteen Abordei sua FatRatpergunta muito importante na última seção. Eu também aprimorei toda a resposta, embora a única grande mudança seja o FatRatmaterial. (Btw, eu percebo que grande parte da minha resposta é realmente tangencial à sua pergunta original; confio que você não se importou de escrever todo o material extra para me divertir e talvez ser interessante para os leitores posteriores.)
raiph
Obrigado pelo esforço extra. Portanto, a .FatRatextensão deve ser colocada dentro do gerador de código. Agora eu tentei com FatRatadicionado dessa maneira e calculou o e com a precisão de mais de 1000 dígitos. A penugem extra adicionada é durante o tempo. Por exemplo, eu não sabia que sayestava truncando as longas matrizes / seqüências. É bom saber essas informações.
Lars Malmsteen
@LarsMalmsteen :) "Portanto, a .FatRatextensão deve ser colocada dentro do gerador de código.". Sim. De maneira mais geral, se uma expressão que envolve divisão já foi avaliada, é tarde demais para desfazer o dano causado se ela exceder Rata precisão. Se houver, ele será avaliado como um Num(flutuador) e isso, por sua vez, prejudica quaisquer outros cálculos envolvendo-o, fazendo-os também Num . A única maneira de garantir que as coisas permaneçam FatRaté iniciá- las FatRate evitar qualquer Nums. Ints e Rats estão OK, desde que haja pelo menos um FatRatpara avisar Raku para manter FatRats.
raiph 18/01
9

Há frações em $_. Assim, você precisa 1 / (1/$_ * $a++)ou melhor $_ /$a++.

Por Raku, você poderia fazer esse cálculo passo a passo

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
fonte
Agradável. Eu não tinha ideia andthen.
Holli