9

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

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

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

但它没有成功。如何正确执行?

4

2 回答 2

11

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

一个班轮一个字母1

say e; # 2.718281828459045

《关于多种方式的论文》2

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

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

作为开胃菜,以下是文章大约一半的引述:

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

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

sub Σ (Unary $block --> Numeric) {
  (0..∞).map($block).produce(&[+]).&converge
}

分析你的代码

这是第一行,生成系列:

my @e = 1, { state $a=1; 1 / ($_ * $a++) } ... *;

闭包 ( { 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)which is 0.666667。那个词应该是1 / (1 * 2 * 3)。哎呀。

使您的代码与公式匹配

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

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

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

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

sub postfix:<!> (\k) { [×] 1 .. k }

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

这是以下的简写:

sub postfix:<!> (\k) { 1 × 2 × 3 × .... × k }

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

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

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

my @e = 1, { state $a=1; 1 / $a++! } ... *;

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

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

从您的代码派生的一个班轮

也许将三行压缩为一行:

say [+] .[^10] given 1, { 1 / [×] 1 .. ++$ } ... Inf

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

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

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

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

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

say [+] .[^500] given 1, { 1.FatRat / [×] 1 .. ++$ } ... Inf

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

脚注

1 Raku 内置了一些重要的数学常数,包括eipi(及其别名π)。因此,人们可以在 Raku 中写出欧拉恒等式,就像在数学书中看到的那样。归功于RosettaCode 的 Euler's Identity 的 Raku 条目

# There's an invisible character between <> and i⁢π character pairs!
sub infix:<⁢&gt; (\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 Damian 的文章是必读的。但这只是谷歌搜索“raku“欧拉数”的 100 多个匹配项中的几种令人钦佩的处理方法之一。

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

于 2020-01-17T22:42:01.367 回答
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
于 2020-01-17T22:45:55.353 回答