用Perl逼近pi – 我做错了什么?

前端之家收集整理的这篇文章主要介绍了用Perl逼近pi – 我做错了什么?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我试图使用 Ramanujan algorithm近似pi:

它应计算总和,直到最后一笔总和小于1e-15.

这应该只是为了好玩而占用我最多半小时的时间……但我的代码并没有产生任何接近pi的东西,我不知道为什么.我可能忽略了一些可能是愚蠢的东西,但不确定!

只是注意:我在1开始$k因为0打破了我的factorialsub而且从我的计算中k = 0无论如何都会返回0.

我意识到代码可以更有效地编写;我尽可能简单地写出来,看看能不能理解我哪里出错了.任何帮助赞赏!

  1. #!/usr/bin/perl
  2. use warnings;
  3. use strict;
  4.  
  5. sub approx_pi {
  6. my $const = (2 * sqrt(2)) / 9801;
  7.  
  8. my $k = 1;
  9. my $sum = 0;
  10. while ($sum < 1e-15) {
  11. my $p1 = factorial((4 * $k),1);
  12. my $p2 = 1103 + (26390 * $k);
  13. my $p3 = (factorial($k,1))**4;
  14. my $p4 = 396**(4 * $k);
  15.  
  16. $sum = $sum + ( ($p1 * $p2) / ($p3 * $p4) );
  17.  
  18. $k++;
  19. }
  20.  
  21. #print "Const: $const\nSum: $sum\n";
  22. return (1 / ($const * $sum));
  23. }
  24.  
  25. sub factorial {
  26. my ($i,$total) = @_;
  27. return $total if $i == 1;
  28.  
  29. $total = $total * $i;
  30. #print "i: $i total: $total\n";
  31.  
  32. factorial($i-1,$total);
  33. }
  34.  
  35. my $pi = approx_pi();
  36. print "my pi is: $pi\n";

解决方法

更新

脚本有几个问题.

>如果k == 0那么项目是1103.所以从0开始$k,而不是1.为此你应该修改阶乘:

  1. sub factorial {
  2. my ($i,$total) = @_;
  3. return $total if $i <= 1;

>没有必要在factorial中传递产品.它可能是这样的:

  1. sub fact {
  2. my $n = shift;
  3. return 1 if $n == 0 || $n ==1;
  4. return $n * fact($n -1);
  5. }

(请参阅Mark Reed关于原始脚本中可能的tail-call optimization问题的有趣评论.在此答案的最后更多关于它.)

>不是$sum值应小于阈值,而是第k个差异项.
所以在approx_pi中你应该使用这样的东西:

  1. my $Diff = 1;
  2. while ($Diff > 1e-15) {
  3. my $p1 = factorial((4 * $k),1);
  4. my $p2 = 1103 + (26390 * $k);
  5. my $p3 = (factorial($k,1))**4;
  6. my $p4 = 396**(4 * $k);
  7.  
  8. $Diff = ($p1 * $p2) / ($p3 * $p4);
  9. $sum += $Diff;
  10.  
  11. $k++;
  12. }

>但无论如何总是递归地调用阶乘并且计算4k的396次幂是无效的,所以它们可以被忽略.

  1. sub approx_pi {
  2. my $const = 4900.5 / sqrt(2);
  3.  
  4. my $k = 0;
  5. my $k4 = 0;
  6. my $F1 = 1;
  7. my $F4 = 1;
  8. my $Pd = 396**4;
  9. my $P2 = 1103;
  10. my $P4 = 1;
  11. my $sum = 0;
  12.  
  13. while (1) {
  14. my $Diff = ($F4 * $P2) / ($F1**4 * $P4);
  15. $sum += $Diff;
  16. last if $Diff < 1e-15;
  17.  
  18. ++$k;
  19. $k4 += 4;
  20. $F1 *= $k;
  21. $F4 *= ($k4 - 3)*($k4 - 2)*($k4 - 1)*$k4;
  22. $P2 += 26390;
  23. $P4 *= $Pd;
  24. }
  25.  
  26. return $const / $sum;
  27. }

结果是:

  1. my pi is: 3.14159265358979

我做了一些措施. Approx_pi函数运行了1 000 000次.固定的原始版本需要24秒,另一个需要5秒.对我而言,有趣的是,$F1 ** 4比$F1 * $F1 * $F1 * $F1更快.

关于阶乘的一些话.由于Mark的评论,我尝试了不同的实现,以找到最快的解决方案.为不同的实现运行了5 000 000个循环来计算15!:

>递归版

  1. sub rfact;
  2. sub rfact($) {
  3. return 1 if $_[0] < 2;
  4. return $_[0] * rfact $_[0] - 1 ;
  5. }

46.39秒

>简单的循环版本

  1. sub lfact($) {
  2. my $f = 1;
  3. for(my $i = 2; $i <= $_[0]; ++$i) { $f *= $i }
  4. return $f;
  5. }

16.29秒

>使用call-tail优化的递归(call _fact 15,1):

  1. sub _fact($$) {
  2. return $_[1] if $_[0] < 2;
  3. @_ = ($_[0] - 1,$_[0] * $_[1]);
  4. goto &_fact;
  5. }

108.15秒

>存储中间值的递归

  1. my @h = (1,1);
  2. sub hfact;
  3. sub hfact($) {
  4. return $h[$_[0]] if $_[0] <= $#h;
  5. return $h[$_[0]] = $_[0] * hfact $_[0] - 1;
  6. }

3.87秒

>循环存储中间值.速度与之前相同,因为只有第一次必须运行.

  1. my @h = (1,1);
  2. sub hlfact($) {
  3. if ($_[0] > $#h) {
  4. my $f = $h[-1];
  5. for (my $i = $#h + 1; $i <= $_[0]; ++$i) { $h[$i] = $f *= $i }
  6. }
  7. return $h[$_[0]];
  8. }

猜你在找的Perl相关文章