使用 Raku 计算 e 数

Calculating the e number using Raku

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

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

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

但是没有成功。如何正确操作?

我在分析你的代码部分分析了你的代码。在此之前,我展示了一些有趣的奖金部分 material。

一个衬里一个字母1

say e; # 2.718281828459045

"A treatise on multiple ways"2

点击上方link查看Damian Conway关于Raku计算e的非凡文章。

文章很好玩(毕竟是达米安)。这是一个非常容易理解的计算讨论 e。这是对 Raku 对 Larry Wall 所拥护的 TIMTOWTDI 哲学的重生的致敬。​​3

作为开胃菜,引用文章中途的一句话:

Given that these efficient methods all work the same way—by summing (an initial subset of) an infinite series of terms—maybe it would be better if we had a function to do that for us. And it would certainly be better if the function could work out by itself exactly how much of that initial subset of the series it actually needs to include in order to produce an accurate answer...rather than requiring us to manually comb through the results of multiple trials to discover that.

And, as so often in Raku, it’s surprisingly easy to build just what we need:

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

正在分析您的代码

这是生成系列的第一行:

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

闭包 ({ code goes here }) 计算一个项。闭包有一个签名,无论是隐式的还是显式的,它决定了它将接受多少参数。在这种情况下,没有明确的签名。 $_ (the "topic" variable) 的使用导致隐式签名需要一个绑定到 $_.

的参数

序列运算符 (...) 重复调用其左侧的闭包,将前一项作为闭包的参数传递,以 lazily 构建一系列术语,直到其右侧的端点,在这种情况下,*、shorthand 表示 Inf 又名无穷大。

第一次调用闭包的主题是1。所以闭包计算 returns 1 / (1 * 1) 产生序列中的前两项作为 1, 1/1.

second 调用中的主题是前一个 1/1 的值,即再次 1。所以闭包计算 returns 1 / (1 * 2),将系列扩展到 1, 1/1, 1/2。看起来都不错。

下一个闭包计算 1 / (1/2 * 3)0.666667。该术语应为 1 / (1 * 2 * 3)。哎呀

使您的代码与公式匹配

您的代码应该符合以下公式:

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

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

让我们创建一个阶乘 post固定运算符:

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

(× 是一个中缀乘法运算符,比通常的 ASCII 中缀 * 更漂亮 Unicode alias。)

那是 shorthand 用于:

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

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

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

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

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

宾果游戏。这会产生正确的系列。

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

从您的代码派生出的一行代码

也许可以将三行压缩为一行:

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

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

我已经从计算下一项的闭包中删除了 $a。一个单独的 $(state $) 相同,是一个匿名状态标量。我将它设为预增量而不是 post-增量,以实现与将 $a 初始化为 1.

相同的效果

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

只要它的操作数都不是 Num (a float, and thus approximate), the / operator normally returns a 100% accurate Rat(有限精度有理数)。但是,如果结果的分母超过 64 位,则该结果将转换为 Num——以性能换取准确性,这是我们不想做出的折衷。我们需要考虑到这一点。

要指定 无限精度 以及 100% 准确度,只需强制操作使用 FatRat 即可。要正确执行此操作,只需(至少)使其中一个操作数成为 FatRat(并且 none 其他操作数成为 Num):

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

我已将其验证为 500 位十进制数字。我希望它能保持准确,直到程序因超出 Raku 语言或 Rakudo 编译器的某些限制而崩溃。 (请参阅 了解相关讨论。)

脚注

1 Raku 内置了一些重要的数学常数,包括 eipi(及其别名 π).因此,人们可以像在数学书上看到的那样在 Raku 中写出欧拉恒等式。感谢 RosettaCode's Raku entry for Euler's Identity:

# 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 Damian 的文章是必读的。但这只是 google for 'raku "euler's number"'.

的 100 多场比赛中的几个令人钦佩的治疗之一

3TIMTOWTDI vs TSBO-APOO-OWTDI for one of the more balanced views of TIMTOWTDI written by a fan of python. But there are downsides to taking TIMTOWTDI too far. To reflect this latter "danger", the Perl community coined the humorously long, unreadable, and understated TIMTOWTDIBSCINABTE -- There Is More Than One Way To Do It But Sometimes Consistency Is Not A Bad Thing Either, pronounced "Tim Toady Bicarbonate". Strangely enough,Larry将碳酸氢盐应用于Raku的设计,Damian将其应用于Raku的计算e

$_中有分数。因此你需要 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