UPDATED
There are several problems with the script.
if
k==0 , then element
1103 . So, run
$k from 0, not 1. To do this, you need to change
factorial :
sub factorial { my ($i, $total) = @_; return $total if $i <= 1;
There is no need to transfer the product in factorial. It could be something like this:
sub fact { my $n = shift; return 1 if $n == 0 || $n ==1; return $n * fact($n -1); }
(See Mark Reedβs wonderful comment about possible tail optimization problems in the original script. For more on this, see the answer at the end.)
The values ββof
$sum should not be lower than the threshold value, but of the k-th difference element. Therefore, in
approx_pi you should use something like this:
my $Diff = 1; while ($Diff > 1e-15) { my $p1 = factorial((4 * $k), 1); my $p2 = 1103 + (26390 * $k); my $p3 = (factorial($k, 1))**4; my $p4 = 396**(4 * $k); $Diff = ($p1 * $p2) / ($p3 * $p4); $sum += $Diff; $k++; }
But in any case, the
factorial call is always recursive and the calculation of
396 power of 4k inefficient, so you can simply turn them off.
sub approx_pi { my $const = 4900.5 / sqrt(2); my $k = 0; my $k4 = 0; my $F1 = 1; my $F4 = 1; my $Pd = 396**4; my $P2 = 1103; my $P4 = 1; my $sum = 0; while (1) { my $Diff = ($F4 * $P2) / ($F1**4 * $P4); $sum += $Diff; last if $Diff < 1e-15; ++$k; $k4 += 4; $F1 *= $k; $F4 *= ($k4 - 3)*($k4 - 2)*($k4 - 1)*$k4; $P2 += 26390; $P4 *= $Pd; } return $const / $sum; }
Result:
my pi is: 3.14159265358979
I did some action. The approx_pi function has approx_pi run 1,000,000 times. The fixed original version takes 24 seconds, the other takes 5 seconds. For me itβs somehow interesting that $F1**4 faster than $F1*$F1*$F1*$F1 .
A few words about factorial. Due to Mark's comment, I tried different implementations to find the fastest solution. 5,000,000 cycles were performed for various implementations to calculate 15! :
Recursive version
sub rfact; sub rfact($) { return 1 if $_[0] < 2; return $_[0] * rfact $_[0] - 1 ; }
46.39 s
Simple version of the loop
sub lfact($) { my $f = 1; for(my $i = 2; $i <= $_[0]; ++$i) { $f *= $i } return $f; }
16.29 s
Recursion with call-tail optimization (call
_fact 15,1 ):
sub _fact($$) { return $_[1] if $_[0] < 2; @_ = ($_[0] - 1, $_[0] * $_[1]); goto &_fact; }
108.15 sec
Intermediate Recursion
my @h = (1, 1); sub hfact; sub hfact($) { return $h[$_[0]] if $_[0] <= $#h; return $h[$_[0]] = $_[0] * hfact $_[0] - 1; }
3.87 s
Loop preserving intermediate values. The speed is the same as the previous one, since it should be performed only for the first time.
my @h = (1, 1); sub hlfact($) { if ($_[0] > $#h) { my $f = $h[-1]; for (my $i = $#h + 1; $i <= $_[0]; ++$i) { $h[$i] = $f *= $i } } return $h[$_[0]]; }