ETOOBUSY 🚀 minimal blogging for the impatient
PWC167 - Gamma Function
TL;DR
On with TASK #2 from The Weekly Challenge #167. Enjoy!
The challenge
Implement subroutine
gamma()
using the Lanczos approximation method.Example
print gamma(3); # 1.99 print gamma(5); # 24 print gamma(7); # 719.99
The questions
This is a quite specific request! I wonder whether we have to reproduce the results or if something a bit more precise is good…
The solution
The wikipedia page is clear:
The Lanczos approximation was popularized by Numerical Recipes
Indeed, chapter 6 of the book has an implementation of the logarithm of the gamma function in the C language, which is extremely easy to translate into Perl. So here we go:
#!/usr/bin/env perl
use v5.24;
use warnings;
use experimental 'signatures';
no warnings 'experimental::signatures';
say "$_ -> @{[ gamma($_) ]}" for @ARGV;
sub gamma ($x) { exp(gammaln($x)) }
sub gammaln ($x) {
die "bad argument in gammaln\n" if $x <= 0;
state $cof = [
57.1562356658629235, -59.5979603554754912,
14.1360979747417471, -0.491913816097620199,
.339946499848118887e-4, .465236289270485756e-4,
-.983744753048795646e-4, .158088703224912494e-3,
-.210264441724104883e-3, .217439618115212643e-3,
-.164318106536763890e-3, .844182239838527433e-4,
-.261908384015814087e-4, .368991826595316234e-5
];
my $y = $x;
my $tmp = $x + 5.24218750000000000;
$tmp = ($x + 0.5) * log($tmp) - $tmp;
my $ser = 0.999999999999997092;
$ser += $_ / ++$y for $cof->@*;
return $tmp + log(2.5066282746310005 * $ser / $x);
} ## end sub gammaln ($x)
The Raku translation is straightforward:
#!/usr/bin/env raku
use v6;
sub MAIN (*@args) {
put "$_ -> {gamma($_)}" for @args;
}
sub gamma ($x) { exp(gammaln($x)) }
sub gammaln (Numeric:D $x where * > 0) {
state @cof =
57.1562356658629235, -59.5979603554754912,
14.1360979747417471, -0.491913816097620199,
.339946499848118887e-4, .465236289270485756e-4,
-.983744753048795646e-4, .158088703224912494e-3,
-.210264441724104883e-3, .217439618115212643e-3,
-.164318106536763890e-3, .844182239838527433e-4,
-.261908384015814087e-4, .368991826595316234e-5;
my $tmp = $x + 5.24218750000000000;
$tmp = ($x + 0.5) * log($tmp) - $tmp;
my $y = $x;
my $ser = 0.999999999999997092;
$ser += $_ / ++$y for |@cof;
return $tmp + log(2.5066282746310005 * $ser / $x);
} ## end sub gammaln ($x)
It’s interesting that the outputs are a little different, I guess that this is because they treat precision in a different way:
$ perl perl/ch-2.pl 3 5 7
3 -> 2
5 -> 24
7 -> 720
$ raku raku/ch-2.raku 3 5 7
3 -> 2.0000000000000018
5 -> 24.000000000000014
7 -> 720.0000000000008
Stay safe!