Estou tentando calcular a constante e (número do AKA Euler ) calculando a fórmula
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?
code-generation
lazy-evaluation
raku
eulers-number
Lars Malmsteen
fonte
fonte
$_
, na tentativa de construir o fatorial. Era obviamente redundante. Na solução correta abaixo,$_
foi descartado e funcionou perfeitamente.Respostas:
Analiso seu código na seção Analisando seu código . Antes disso, apresento algumas seções divertidas de material bônus.
Um forroUma letra 1"Um tratado de várias maneiras" 2
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. 3Como aperitivo, aqui está uma citação de aproximadamente na metade do artigo:
Analisando seu código
Aqui está a primeira linha, gerando a série:
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 deInf
aka infinito.O tópico na primeira chamada para o encerramento é
1
. Portanto, o fechamento calcula e retorna1 / (1 * 1)
produzindo os dois primeiros termos da série como1, 1/1
.O tópico na segunda chamada é o valor da anterior
1/1
, ou seja,1
novamente. Portanto, o fechamento calcula e retorna1 / (1 * 2)
, estendendo a série para1, 1/1, 1/2
. Tudo parece bom.O próximo fechamento calcula
1 / (1/2 * 3)
qual é0.666667
. Esse termo deveria ser1 / (1 * 2 * 3)
. OpaFazendo 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:
(
×
é 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:
(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 areduce 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:
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:
.[^10]
aplica-se ao tópico, definido pelogiven
. (^10
é uma abreviação de0..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
a1
.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ãoRat
(um racional de precisão limitado). Mas se o denominador do resultado exceder 64 bits, esse resultado será convertido em umNum
- 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 umFatRat
(e nenhum outro ser umNum
):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
epi
(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 :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.fonte
$
era uma abreviação parastate $
, é bastante útil.e
3ª 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.FatRat
pergunta muito importante na última seção. Eu também aprimorei toda a resposta, embora a única grande mudança seja oFatRat
material. (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.).FatRat
extensão deve ser colocada dentro do gerador de código. Agora eu tentei comFatRat
adicionado 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 quesay
estava truncando as longas matrizes / seqüências. É bom saber essas informações..FatRat
extensã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 excederRat
a precisão. Se houver, ele será avaliado como umNum
(flutuador) e isso, por sua vez, prejudica quaisquer outros cálculos envolvendo-o, fazendo-os tambémNum
. A única maneira de garantir que as coisas permaneçamFatRat
é iniciá- lasFatRat
e evitar qualquerNum
s.Int
s eRat
s estão OK, desde que haja pelo menos umFatRat
para avisar Raku para manterFatRat
s.Há frações em
$_
. Assim, você precisa1 / (1/$_ * $a++)
ou melhor$_ /$a++
.Por Raku, você poderia fazer esse cálculo passo a passo
fonte
andthen
.