使用 Raku 计算 e 数

Lar*_*een 9 code-generation lazy-evaluation eulers-number raku

我试图通过计算公式来计算e常数(又名欧拉数电子

为了一次性计算阶乘和除法,我写了这个:

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

但它没有成功。如何正确地做到这一点?

rai*_*iph 11

我在分析您的代码部分分析了您的代码。在此之前,我介绍了一些有趣的奖励材料部分。

一个班轮一个字母1

say e; # 2.718281828459045
Run Code Online (Sandbox Code Playgroud)

《论多种方式》2

单击上面的链接可查看 Damian Conway 关于eRaku计算的非凡文章。

这篇文章很有趣(毕竟是达米安)。这是一个非常容易理解的关于计算的讨论e。这是对拉里·沃尔 (Larry Wall) 所信奉的 TIMTOWTDI 哲学的 Raku 碳酸氢盐转世的致敬。3

作为开胃菜,这里引用了文章大约一半的内容:

鉴于这些有效的方法都以相同的方式工作——通过对无限系列的项(的初始子集)求和——也许如果我们有一个函数来为我们做这件事会更好。如果该函数能够自己计算出它实际上需要包含多少系列的初始子集才能产生准确的答案,那肯定会更好……而不是要求我们手动梳理结果多次试验以发现这一点。

而且,就像在 Raku 中一样,构建我们需要的东西非常容易:

sub ? (Unary $block --> Numeric) {
  (0..?).map($block).produce(&[+]).&converge
}
Run Code Online (Sandbox Code Playgroud)

分析你的代码

这是第一行,生成系列:

my @e = 1, { state $a=1; 1 / ($_ * $a++) } ... *;
Run Code Online (Sandbox Code Playgroud)

闭包 ( { code goes here }) 计算一个术语。闭包有一个隐式或显式的签名,它决定了它将接受多少个参数。在这种情况下,没有明确的签名。使用$_“主题”变量)会产生一个隐式签名,该签名需要一个绑定到 的参数$_

序列运算符 ( ...) 重复调用其左侧的闭包,将前一项作为闭包的参数传递,以懒惰地构建一系列术语,直到其右侧的端点,在本例中是*Inf又名无穷大的简写。

第一次调用闭包的主题是1。因此,闭包计算并返回1 / (1 * 1)将系列中的前两项作为1, 1/1

第二个调用中的主题是前一个的值1/1,即1再次。因此闭包计算并返回1 / (1 * 2),将系列扩展到1, 1/1, 1/2。这一切看起来都不错。

下一个闭包计算1 / (1/2 * 3)哪个是0.666667。那个词应该是1 / (1 * 2 * 3)。哎呀。

使您的代码与公式匹配

您的代码应该与公式匹配:
电子

在这个公式中,每一项都是根据它在系列中的位置来计算的。系列中的第k项(其中第一个k =0 1)只是阶乘k的倒数。

(因此它与前一项的无关。因此$_,接收前一项的的 不应在闭包中使用。)

让我们创建一个阶乘后缀运算符:

sub postfix:<!> (\k) { [×] 1 .. k }
Run Code Online (Sandbox Code Playgroud)

(×是一个中缀乘法运算符,是通常 ASCII 中缀的一个更好看的Unicode 别名*。)

这是以下的简写:

sub postfix:<!> (\k) { 1 × 2 × 3 × .... × k }
Run Code Online (Sandbox Code Playgroud)

(我在大括号内使用了伪元句法符号来表示根据需要添加或减去尽可能多的术语的想法。

更一般地,将中缀运算符op放在表达式开头的方括号中会形成一个复合前缀运算符,它等效于reduce with => &[op],。有关更多信息,请参阅归约元运算符

现在我们可以重写闭包以使用新的阶乘后缀运算符:

my @e = 1, { state $a=1; 1 / $a++! } ... *;
Run Code Online (Sandbox Code Playgroud)

答对了。这会产生正确的系列。

......直到它没有,出于不同的原因。下一个问题是数字准确性。但是让我们在下一节中讨论这个问题。

从您的代码派生的单行

也许将三行压缩为一行:

say [+] .[^10] given 1, { 1 / [×] 1 .. ++$ } ... Inf
Run Code Online (Sandbox Code Playgroud)

.[^10]适用于由 设置的主题given。(^10是 的简写0..9,因此上面的代码计算系列中前十项的总和。)

我已经$a从闭包计算中消除了下一个学期。单独$的与(state $),匿名状态标量相同。我将其设为预增量而不是后增量,以达到与初始化$a1.

我们现在剩下最后一个(大!)问题,您在下面的评论中指出。

如果其操作数都不是 a Num(浮点数,因此是近似值),则/运算符通常返回 100% 准确度Rat(有限精度的有理数)。但是,如果结果的分母超过 64 位,则该结果将转换为 a Num—— 以性能换取准确性,这是我们不想进行的权衡。我们需要考虑到这一点。

要指定无限精度以及 100% 准确度,只需强制操作使用FatRats。要正确执行此操作,只需(至少)将其中一个操作数设为 a FatRat(而其他操作数均不设为 a Num):

say [+] .[^500] given 1, { 1.FatRat / [×] 1 .. ++$ } ... Inf
Run Code Online (Sandbox Code Playgroud)

我已将此验证为 500 位十进制数字。我希望它保持准确,直到程序因超出 Raku 语言或 Rakudo 编译器的某些限制而崩溃。(请参阅我对无法将 65536 位宽的 bigint 拆箱为原生整数的回答,以进行一些讨论。)

脚注

1乐具有内置的,包括一些重要的数学常数eipi(和它的别名?)。因此,人们可以在 Raku 中编写欧拉恒等式,有点像在数学书上的样子。归功于RosettaCode 对 Euler 身份的 Raku 条目

# 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
Run Code Online (Sandbox Code Playgroud)

2 Damian 的文章是必读的。但这只是谷歌搜索 'raku“euler's number”'的 100 多个匹配项中的几种令人钦佩的处理方法之一。

3请参阅TIMTOWTDI 与 TSBO-APOO-OWTDI以了解由 Python 粉丝编写的更平衡的 TIMTOWTDI 视图之一。但是,将 TIMTOWTDI 走得太远也不利之处。为了反映后一种“危险”,Perl 社区创造了幽默的长、不可读和低调的TIMTOWTDIBSCINABTE —— 有不止一种方法可以做到,但有时一致性也不是坏事,发音为“Tim Toady Bicarbonate”。奇怪的是,Larry 将碳酸氢盐应用于 Raku 的设计,而 Damian 将其应用于 Raku 的计算e


wam*_*mba 9

中有分数$_。因此,您需要1 / (1/$_ * $a++)或者更确切地说$_ /$a++

通过 Raku 你可以一步一步地做这个计算

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
Run Code Online (Sandbox Code Playgroud)