我正在从Perl5学习Perl6。
为了进行编译,我将发布整个程序:
sub lgamma ( Num(Real) \n --> Num ){
use NativeCall;
sub lgamma (num64 --> num64) is native {}
lgamma( n )
}
sub pvalue (@a, @b) {
if @a.elems <= 1 {
return 1.0;
}
if @b.elems <= 1 {
return 1.0;
}
my Rat $mean1 = @a.sum / @a.elems;
my Rat $mean2 = @b.sum / @b.elems;
if $mean1 == $mean2 {
return 1.0;
}
my Rat $variance1 = 0.0;
my Rat $variance2 = 0.0;
for @a -> $i {
$variance1 += ($mean1 - $i)**2#";" unnecessary for last statement in block
}
for @b -> $i {
$variance2 += ($mean2 - $i)**2
}
if ($variance1 == 0 && $variance2 == 0) {
return 1.0;
}
$variance1 /= (@a.elems - 1);
$variance2 /= (@b.elems - 1);
my $WELCH_T_STATISTIC = ($mean1-$mean2)/sqrt($variance1/@a.elems+$variance2/@b.elems);
my $DEGREES_OF_FREEDOM = (($variance1/@a.elems+$variance2/@b.elems)**2)
/
(
($variance1*$variance1)/(@a.elems*@a.elems*(@a.elems-1))+
($variance2*$variance2)/(@b.elems*@b.elems*(@b.elems-1))
);
my $A = $DEGREES_OF_FREEDOM/2;
my $value = $DEGREES_OF_FREEDOM/($WELCH_T_STATISTIC*$WELCH_T_STATISTIC+$DEGREES_OF_FREEDOM);
my Num $beta = lgamma($A)+0.57236494292470009-lgamma($A+0.5);
my Rat $acu = 10**(-15);
my ($ai,$cx,$indx,$ns,$pp,$psq,$qq,$rx,$temp,$term,$xx);
# Check the input arguments.
return $value if $A <= 0.0;# || $q <= 0.0;
return $value if $value < 0.0 || 1.0 < $value;
# Special cases
return $value if $value == 0.0 || $value == 1.0;
$psq = $A + 0.5;
$cx = 1.0 - $value;
if $A < $psq * $value {
($xx, $cx, $pp, $qq, $indx) = ($cx, $value, 0.5, $A, 1);
} else {
($xx, $pp, $qq, $indx) = ($value, $A, 0.5, 0);
}
$term = 1.0;
$ai = 1.0;
$value = 1.0;
$ns = $qq + $cx * $psq;
$ns = $ns.Int;
#Soper reduction formula.
$rx = $xx / $cx;
$temp = $qq - $ai;
$rx = $xx if $ns == 0;
while (True) {
$term = $term * $temp * $rx / ( $pp + $ai );
$value = $value + $term;
$temp = $term.abs;
if $temp <= $acu && $temp <= $acu * $value {
$value = $value * ($pp * $xx.log + ($qq - 1.0) * $cx.log - $beta).exp / $pp;
$value = 1.0 - $value if $indx;
last;
}
$ai++;
$ns--;
if 0 <= $ns {
$temp = $qq - $ai;
$rx = $xx if $ns == 0;
} else {
$temp = $psq;
$psq = $psq + 1.0;
}
}
return $value;
}
my @array2d = ([27.5,21.0,19.0,23.6,17.0,17.9,16.9,20.1,21.9,22.6,23.1,19.6,19.0,21.7,21.4],
[27.1,22.0,20.8,23.4,23.4,23.5,25.8,22.0,24.8,20.2,21.9,22.1,22.9,20.5,24.4],#0.
[17.2,20.9,22.6,18.1,21.7,21.4,23.5,24.2,14.7,21.8],
[21.5,22.8,21.0,23.0,21.6,23.6,22.5,20.7,23.4,21.8,20.7,21.7,21.5,22.5,23.6,21.5,22.5,23.5,21.5,21.8],
[19.8,20.4,19.6,17.8,18.5,18.9,18.3,18.9,19.5,22.0],
[28.2,26.6,20.1,23.3,25.2,22.1,17.7,27.6,20.6,13.7,23.2,17.5,20.6,18.0,23.9,21.6,24.3,20.4,24.0,13.2],
[30.02,29.99,30.11,29.97,30.01,29.99],
[29.89,29.93,29.72,29.98,30.02,29.98],
[3.0,4.0,1.0,2.1],
[490.2,340.0,433.9],
[<1.0/15.0>, <10.0/62.0>],
[<1.0/10>, <2/50.0>],
[0.010268,0.000167,0.000167],
[0.159258,0.136278,0.122389],
[9/23.0,21/45.0,0/38.0],
[0/44.0,42/94.0,0/22.0]);
say @array2d[11][0];
my @CORRECT_ANSWERS = (0.021378001462867,
0.148841696605327,
0.0359722710297968,
0.090773324285671,
0.0107515611497845,
0.00339907162713746,
0.52726574965384,
0.545266866977794);
my UInt $i = 0;
my Real $error = 0.0;
for @array2d -> @left, @right {
my $pvalue = pvalue(@left, @right);
$error += ($pvalue - @CORRECT_ANSWERS[$i]).abs;
say "$i [" ~ @left.join(',') ~ '] [' ~ @right ~ "] = $pvalue";
if $error > 10**-9 {
say "\$p = $pvalue, but should be @CORRECT_ANSWERS[$i]";
die;
}
# printf("Test sets %u p-value = %.14g\n",$i+1,$pvalue);
$i++
}
printf("the cumulative error is %g\n", $error);
此子数组与众不同的原因是它具有用于分隔的“ /”。如何让Perl6 for循环评估此子数组?
编辑:我正在努力构成一个最小的工作示例。我正在发布整个代码,以便对其进行编译。
答案 0 :(得分:8)
(事实证明,这个答案完全错过了问题@con。但是我不会删除它,因为它收集了一些关于有理数值的希望有用的链接。)
为什么数组在声明内跳过计算值?
不是。
我正在学习Perl6 ...
在数学和Perl 6中,诸如1.3
之类的数字都是小数。一些解释:
My Medium comment on Baking rationals into a programming language。
P6文档的Numerics page。 (由Zoffix撰写。感谢您为P6 Zoffix所做的大量出色工作。)
My SO answer的问题“ Perl 6的性能是否因使用十进制数字的理性而受到影响”。
...来自Perl5
与此有关的事情属于the Perl 5 to Perl 6 guide docs中的一个。您愿意打开a new doc issue吗?
[1.0/15.0, 10.0/62.0],#this isn't evaluated
[1.0/10, 2/50.0],#neither is this
它们都经过评估。
在数学和P6中,文字1.0
是十进制是有道理的。 foo / bar
也是有道理的。
(好吧,如果foo
和bar
都是整数或有理数,并且结果的分母保持64位以下或为foo
或{{1}中的一个}是FatRat
有理数的任意精度。)
但是,Perl6似乎不喜欢我在这里指出的值。
您还没有解释您看到的东西使您认为P6不喜欢它们的情况。
很有可能是,根据Brad Gilbert++'s comment,您看到的是bar
之类的值,而不是<1/15>
。前者是P6文字值,可以100%准确地表示0.066667
。要100%准确地表示,十进制显示必须为1/15
,并带有0.06666666666666...
或末尾的某个类似字符,以表示无限重复的最终...
。但是6
表示相同的数字,并且更短且更简单,因此<1/15>
和dd
使用.perl
形式代替。
在声明2D数组时,如何让Perl6评估这样的表达式?
您无需执行任何操作。它正在评估他们。 :)
答案 1 :(得分:6)
(这是一个nanswer,本身不是一个答案。它最初是在@con重新编写了他们的问题以包括他们的所有代码之后写的,这是迈向我的Third and final answer的一步。现在希望对于那些学习Perl 6的人来说是有用的资源。)
这是很多代码! :)
到目前为止,我的想法是:您是否绝对肯定地100%确定您还没有错过一些输入数据?您似乎比P6跳过数据的可能性要大得多,尤其是考虑到计算得出的值恰好是您期望获得下一个正确结果的值。
(更新确实确实是问题出在输入数据不正确。)
此nanswer的其余部分是问题代码的逐行“清理”(而非重构)。我有两个目标,第二个是最重要的,亲爱的读者,请阅读以下内容:
我的翻译有效地向我证明并向@con证明我已经考虑了他们的所有代码。这是为了减少有关错误可能在哪里的不确定性。请注意,我的大部分更改可能与它们的错误没有直接关系,但是直到完成重写后,我才感到难以接受。
@con的代码以及我的翻译可能对学习Perl 6的任何人有用。。 @con的代码是Perl 5 code的P6转换。 P5代码又是C code的翻译。该代码还有其他翻译成其他语言的版本。我的代码接受@con的翻译,并将其翻译成更惯用的版本,并附有解释我为何更改其代码的注释。
My translation of @con's code, minus my commentary (see below), in tio。
(我最初的计划是,我们将基于在tio中修改代码并共享指向修改后的tio版本的链接来进一步研究@con的错误。为此,您/他们只需在tio中单击位于以下位置的链接图标单击顶部的()即可获得指向代码的链接(当单击链接图标时。)Tio非常适合能够编写,编辑和运行以P6和其他语言编写的代码,然后进行共享。)
我已经离开了开始lgamma
例程。我在脚注中写了很多评论,主要针对其他读者,因为它包含了大量有趣的功能 1 :
sub lgamma ( Num(Real) \n --> Num ){
use NativeCall;
sub lgamma (num64 --> num64) is native {}
lgamma( n )
}
我注意到lgamma
代码全都与浮点数有关(在P6中为Num
/ num...
类型)。
sub pvalue (@a, @b) {
对于不了解Perls的用户,sub
关键字引入了子例程(又称函数)。
这个有两个“ listy”(Positional)参数。
(当您看到@
(例如,以"sigil"的@foo
或例如@($bar)
的操作符)时,请考虑“列表”。 )
为了加快代码读取速度并减少重复代码,我将其替换为:
if @a.elems <= 1 {
return 1.0;
}
if @b.elems <= 1 {
return 1.0;
}
与此:
return 1 if @a | @b <= 1;
如果列表1
或列表@a
中的元素数小于或等于1,则用英语将其读为“返回@b
”。
我使用|
运算符构造了一个any
junction。
(不要浪费时间试图围绕结点的作用原理以及它们如何做的理论。只需以简单和/或简洁的方式在实践中使用它们即可。如果保持简单,请阅读以一种简单的方式,它们很棒,并且只做了显而易见的事情。如果使用它们使代码在特定情况下不明显,则可以考虑针对该特定情况使用其他构造。)
数字上下文中的数组将求值为其元素数。像<=
这样的数字运算符会强加数字上下文。因此,我放下了.elems
。
数字上下文和在数字上下文中评估其长度的数组是P6的基本方面,因此这是惯用编码,适用于除最基本的入门新手示例以外的所有示例。
我从1.0
切换到1
。
我的猜测是@con编写了1.0
,因为这是将值按字面意义用它们正在翻译的代码编写的方式,和/或意图是它代表浮点值。但是在P6中,像e
这样的普通十进制文字(没有1.0
指数)会产生有理数。
在此代码中使用1.0
而不是1
将较简单的较快类型(整数)替换为较复杂的较慢类型(理性)。当在 any 组件值为浮点的公式中使用此较慢类型的值时,将强制将其转换为浮点数(比整数慢)。此代码中的大多数或所有公式都会发生这种情况,因为lgamma
返回浮点数。
更一般地说,如果您没有指定变量和值的类型未指定,则P6的工作和读取效果最佳,除非有令人信服的理由指定它们,否则编译器可以自行确定。保留未指定的类型可以减少读者的认知负担,并增加了代码重用和编译器代码优化的灵活性。
这导致一对互补的格言:
默认情况下,保留类型信息隐式 。如果您不知道给定值或变量的类型是什么还是无关紧要的,请不要指定它。
如果您将值,变量或参数的类型设为显式,则P6将使用该类型,强制遵守该类型,无论是否改善代码或不必要地降低代码速度或完全停止代码。
如果您完全知道,您需要使用其他类型或添加类型约束以使代码正确,那么请务必继续。同样,如果您已经已经验证您的代码正确 而没有更具体的类型,但是您想使其变得更快,更安全或更清晰,请继续。但是,如果您不知道,则将其保留为通用名称。正如Ben曾经说过的那样,P6的设计符合您的意思,并且成功的频率将超出您的想象。
与premature optimization相似,在P6中,premature typing is an anti-pattern用一次性代码和长期生产代码来更强有力地说明这一点。
my Rat $mean1 = @a.sum / @a.elems;
my Rat $mean2 = @b.sum / @b.elems;
if $mean1 == $mean2 {
return 1.0;
}
成为:
my (\mean_a, \mean_b) = @a.sum / @a, @b.sum / @b;
return 1 if mean_a == mean_b;
除非我知道需要贴记号,否则我通常将“变量”“减号”。如果“变量”实际上没有变化,则可能不需要标记。
我将$mean1
重命名为mean_a
,因为它显然对应于@a
。
my Rat $variance1 = 0.0;
my Rat $variance2 = 0.0;
for @a -> $i {
$variance1 += ($mean1 - $i)**2#";" unnecessary for last statement in block
}
for @b -> $i {
$variance2 += ($mean2 - $i)**2
}
成为:
my ($vari_a, $vari_b);
$vari_a += (mean_a - $_)² for @a;
$vari_b += (mean_b - $_)² for @b;
将变量$_
读为“ it”。
if ($variance1 == 0 && $variance2 == 0) {
return 1.0;
}
$variance1 /= (@a.elems - 1);
$variance2 /= (@b.elems - 1);
成为:
return 1 unless ($vari_a or $vari_b);
$vari_a /= (@a - 1);
$vari_b /= (@b - 1);
在Perls中,0
在布尔测试上下文中的值为False
。并且像@a
这样的数组或列表在数字上下文中对其元素计数进行求值。
my $WELCH_T_STATISTIC = ($mean1-$mean2)/sqrt($variance1/@a.elems+$variance2/@b.elems);
my $DEGREES_OF_FREEDOM = (($variance1/@a.elems+$variance2/@b.elems)**2)
/
(
($variance1*$variance1)/(@a.elems*@a.elems*(@a.elems-1))+
($variance2*$variance2)/(@b.elems*@b.elems*(@b.elems-1))
);
my $A = $DEGREES_OF_FREEDOM/2;
my $value = $DEGREES_OF_FREEDOM/($WELCH_T_STATISTIC*$WELCH_T_STATISTIC+$DEGREES_OF_FREEDOM);
my Num $beta = lgamma($A)+0.57236494292470009-lgamma($A+0.5);
my Rat $acu = 10**(-15);
my ($ai,$cx,$indx,$ns,$pp,$psq,$qq,$rx,$temp,$term,$xx);
# Check the input arguments.
return $value if $A <= 0.0;# || $q <= 0.0;
成为:
my \WELCH_T_STATISTIC = (mean_a - mean_b)
/ ( $vari_a / @a + $vari_b / @b ).sqrt;
my \DEGREES_OF_FREEDOM = ($vari_a / @a + $vari_b / @b)²
/ ($vari_a² / (@a² * (@a - 1)) + $vari_b² / (@b² * (@b - 1)));
my \A = DEGREES_OF_FREEDOM / 2;
my $value = DEGREES_OF_FREEDOM
/ (WELCH_T_STATISTIC² + DEGREES_OF_FREEDOM);
my \magic-num = 0.57236494292470009;
my \beta = lgamma(A) + magic-num - lgamma(A + 0.5);
my \acu = 1e-15;
# Check the input arguments.
return $value if A <= 0;# || $q <= 0.0;
(上一行中的$q
是什么?)
请注意,我将类型分别放在了beta
和acu
上。分配给beta
变量的值仍然是Num
,因为lgamma
返回了Num
。分配给acu
的值也将是Num
,因为在数字文字中使用e
指数意味着它构造的值是Num
。
return $value if $value < 0.0 || 1.0 < $value;
# Special cases
return $value if $value == 0.0 || $value == 1.0;
成为:
return $value unless $value ~~ 0^..^1;
将0^
读为“大于零”(不包括零),将^1
读为“最大1”(不包括一个)。
~~
是“智能匹配”运算符。如果其右侧的值接受其左侧的值,它将返回True
。
因此,如果$value
小于或等于$value
或大于或等于0
,此return语句将返回1
。
作为一个小小的整理,我移动了一个变量my
的声明,我在上面的重写中删除了变量,直到此时它们才变得相关,并也添加了$ns
:< / p>
my ($ai, $cx, $indx, $pp, $psq, $qq, $rx, $temp, $term, $xx, $ns);
$psq = $A + 0.5;
$cx = 1.0 - $value;
if $A < $psq * $value {
($xx, $cx, $pp, $qq, $indx) = ($cx, $value, 0.5, $A, 1);
} else {
($xx, $pp, $qq, $indx) = ($value, $A, 0.5, 0);
}
成为:
$psq = A + 0.5;
$cx = 1 - $value;
($xx, $cx, $pp, $qq, $indx) =
A < $psq * $value
?? ($cx, $value, 0.5, A, 1)
!! ($value, $cx, A, 0.5, 0);
我将一堆变量的条件赋值重写为ternary,因此更容易看到将什么赋给了什么。
$term = 1.0;
$ai = 1.0;
$value = 1.0;
$ns = $qq + $cx * $psq;
$ns = $ns.Int;
成为:
$term = 1;
$ai = 1;
$value = 1;
$ns = ($qq + $cx * $psq) .Int;
将1.0
替换为1
,并将分配给$ns
的表达式与.Int
强制组合起来。
(我在翻译时从代码中剥离了类型,它继续计算出正确的结果,只是消除了上面的Int
强制使代码变成了infiniloop。这最终导致我搜寻网络以查看是否我可以找到@con正在翻译的代码。那是我在rosettacode.org上找到的。在我看到的代码中,它明确地键入为整数,因此大概是确保算法有效的关键。)
#Soper reduction formula.
$rx = $xx / $cx;
$temp = $qq - $ai;
$rx = $xx if $ns == 0;
(不变)
while (True) {
成为:
loop {
$term = $term * $temp * $rx / ( $pp + $ai );
$value = $value + $term;
$temp = $term.abs;
(不变)
if $temp <= $acu && $temp <= $acu * $value {
$value = $value * ($pp * $xx.log + ($qq - 1.0) * $cx.log - $beta).exp / $pp;
$value = 1.0 - $value if $indx;
last;
}
成为:
if $temp <= acu & acu * $value {
$value = $value * ($pp * $xx.log + ($qq - 1) * $cx.log - beta).exp / $pp;
$value = 1 - $value if $indx;
last;
}
这一次包含结点(&
)的条件用英语读取为“如果temp小于或等于acu和acu times值”。
$ai++;
$ns--;
if 0 <= $ns {
$temp = $qq - $ai;
$rx = $xx if $ns == 0;
} else {
$temp = $psq;
$psq = $psq + 1;
}
}
return $value;
}
我刚刚将1.0
替换为1
。
现在有问题的数组。正如我在开始时所写的那样,我很确定您(或者您的数据提供者)忘记了几行:
my @array2d =
[27.5,21.0,19.0,23.6,17.0,17.9,16.9,20.1,21.9,22.6,23.1,19.6,19.0,21.7,21.4],
[27.1,22.0,20.8,23.4,23.4,23.5,25.8,22.0,24.8,20.2,21.9,22.1,22.9,20.5,24.4],
[17.2,20.9,22.6,18.1,21.7,21.4,23.5,24.2,14.7,21.8],
[21.5,22.8,21.0,23.0,21.6,23.6,22.5,20.7,23.4,21.8,20.7,21.7,21.5,22.5,23.6,21.5,22.5,23.5,21.5,21.8],
[19.8,20.4,19.6,17.8,18.5,18.9,18.3,18.9,19.5,22.0],
[28.2,26.6,20.1,23.3,25.2,22.1,17.7,27.6,20.6,13.7,23.2,17.5,20.6,18.0,23.9,21.6,24.3,20.4,24.0,13.2],
[30.02,29.99,30.11,29.97,30.01,29.99],
[29.89,29.93,29.72,29.98,30.02,29.98],
[3.0,4.0,1.0,2.1],
[490.2,340.0,433.9],
[<1.0/15.0>, <10.0/62.0>],
[<1.0/10>, <2/50.0>],
[0.010268,0.000167,0.000167],
[0.159258,0.136278,0.122389],
[9/23.0,21/45.0,0/38.0],
[0/44.0,42/94.0,0/22.0];
say @array2d[11][0];
这些是谁的回答?您是否100%确定0.0033...
答案与[<1.0/15.0>, <10.0/62.0>],[<1.0/10>, <2/50.0>]
数据一起使用?
my @CORRECT_ANSWERS =
0.021378001462867,
0.148841696605327,
0.0359722710297968,
0.090773324285671,
0.0107515611497845,
0.00339907162713746,
0.52726574965384,
0.545266866977794;
最后,我只是删除了类型,并再次为更漂亮的值加指数(10⁻⁹
)使用上标:
my $i = 0;
my $error = 0;
for @array2d -> @left, @right {
my $pvalue = pvalue(@left, @right);
$error += ($pvalue - @CORRECT_ANSWERS[$i]).abs;
say "$i [" ~ @left.join(',') ~ '] [' ~ @right ~ "] = $pvalue";
if $error > 10⁻⁹ {
say "\$p = $pvalue, but should be @CORRECT_ANSWERS[$i]";
die;
}
# printf("Test sets %u p-value = %.14g\n",$i+1,$pvalue);
$i++
}
printf("the cumulative error is %g\n", $error);
1 喜欢这个不错的lgamma
例程! (结果为Brad Gilbert wrote it。)
sub lgamma ( Num(Real) \n --> Num ){
use NativeCall;
sub lgamma (num64 --> num64) is native {}
lgamma( n )
}
展示:
使用同名的高级P6(Perl 6)例程对低级(C)例程进行阴影处理,以在调用C函数之前添加进一步的处理。
Explicitly converting the input type从较宽的P6类型Real
到较窄的Num
。 Perls是该术语的原始技术定义,"strongly typed",但是P6每几个other interpretations of "strong typing"都会提供附加的选项,并且相对于语言而言,这些语言对这些其他“强类型”的解释能力较弱(如Perl 5,Python和C)。显式类型转换是P6引入的这种功能转变的一部分。
削减印记。我将在本文其他地方进行的相同操作中对此进行进一步讨论。
对库use
进行词法作用域划分。内部lgamma
例程和use Nativecall;
导入的符号在任何地方都看不到,但在包含它的外部lgamma
例程内部则看不到。
使用NativeCall(P6 C FFI)允许高级P6代码直接甜蜜地映射到C代码,包括从P6的IEEE双浮点兼容boxed type { {1}}到与未装箱的machine data type等价的num64
。
全部5行!非常好。 :)
答案 2 :(得分:4)
.o(他希望地说。有人曾写过四个这样的问题答案吗?!?)
如果您在数据中交换这些行:
[<1.0/15.0>, <10.0/62.0>],
[<1.0/10>, <2/50.0>],
[0.010268,0.000167,0.000167],
[0.159258,0.136278,0.122389],
相反,相反:
[0.010268,0.000167,0.000167],
[0.159258,0.136278,0.122389],
[<1.0/15.0>, <10.0/62.0>],
[<1.0/10>, <2/50.0>],
然后,程序的输出(至少是我通过重写代码nanswer编写的程序的输出):
0.159258
0 [27.5,21,19,23.6,17,17.9,16.9,20.1,21.9,22.6,23.1,19.6,19,21.7,21.4] [27.1 22 20.8 23.4 23.4 23.5 25.8 22 24.8 20.2 21.9 22.1 22.9 20.5 24.4] = 0.02137800146286709
1 [17.2,20.9,22.6,18.1,21.7,21.4,23.5,24.2,14.7,21.8] [21.5 22.8 21 23 21.6 23.6 22.5 20.7 23.4 21.8 20.7 21.7 21.5 22.5 23.6 21.5 22.5 23.5 21.5 21.8] = 0.14884169660532756
2 [19.8,20.4,19.6,17.8,18.5,18.9,18.3,18.9,19.5,22] [28.2 26.6 20.1 23.3 25.2 22.1 17.7 27.6 20.6 13.7 23.2 17.5 20.6 18 23.9 21.6 24.3 20.4 24 13.2] = 0.035972271029797116
3 [30.02,29.99,30.11,29.97,30.01,29.99] [29.89 29.93 29.72 29.98 30.02 29.98] = 0.09077332428566681
4 [3,4,1,2.1] [490.2 340 433.9] = 0.010751561149784494
5 [0.010268,0.000167,0.000167] [0.159258 0.136278 0.122389] = 0.003399071627137453
6 [1.0/15.0,10.0/62.0] [1.0/10 2/50.0] = 0.5272657496538401
7 [0.391304,0.466667,0] [0 0.446809 0] = 0.5452668669777938
the cumulative error is 5.50254e-15
回想起来,这很明显是错误的。 2020年事后诸葛亮。 :)