jjw017 has asked for the wisdom of the Perl Monks concerning the following question:

Hi all,

I've been trying to come up with a way to calculate factorials of very large numbers (example: of 2032597) and currently I'm stuck on the implementation of the Stirling's approximation. I tried using bignum but I keep getting an error stating: "Can't use an undefined value as an ARRAY reference at C:/Perl/../Calc.pm"

In this approximation, $exp evaluates to ~2.8e11938980. Is this number too large to be used in perl?

use Math::Trig; use bignum; sub factorial { my $n = @_; my $root = sqrt($n*2*pi); my $exp = ($n/exp(1))^$n; my $result =$root*$exp; return $result; } my $b = 2032597; my $r = Math::BigInt->new(factorial($b)); print "b is $b\n"; print "the calculated result is $r\n";

Replies are listed 'Best First'.
Re: Operations with Extremely Large Numbers
by BrowserUk (Patriarch) on Nov 09, 2011 at 17:25 UTC

    The problem is that your are trying to use the exlusive-or operator ^ to perform exponentiation **.

    This works:

    use Math::Trig; use bignum; sub factorial { my $n = @_; my $root = sqrt($n*2*pi); my $exp = ($n/exp(1)) ** $n; my $result =$root*$exp; return $result; } my $b = 2032597; my $r = Math::BigInt->new(factorial($b)); print "b is $b\n"; print "the calculated result is $r\n";

    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.

      A further thought. If these calculations are in anyway time-critical.

      In almost every formula I've ever encountered that uses huge factorials -- usually probability calculations of one form or another -- there are usually (at least) two factorial (or factorial derived) terms that eventually are divided one by the other so bringing the silly numbers back into the realms of rationality.

      So you tend to have formulae something like:

      r = n! / (n - m)!

      Ostensibly, the calculation goes like this:

      n = 1e9 m = 4 r = ( 1 * 2 * 3 ... 999999999 * 1000000000 ) / ( 5 * 6 * 7 ... 999999999 * 1000000000 )

      Which using arbitrary precision can take long time (and a large amount of memory) to calculate.

      But with cursory inspection it is easy to see that the result is just 1/ ( 1* 2 * 3 * 4 ) == 1/24 == 0.041666666666666666666666666666667 .

      If your application requires the calculation of large numbers of these types of formulae, and timeliness is of any consideration, it makes sense to avoid the huge numbers by deferring the actual math until you've performed some "cancelling out".

      In most cases, the avoidance of arbitrary precision more than compensates for the extra effort and care required.

        Thanks BrowserUk. I literally bonked myself in the head after reading this. I'm going to try giving careful consideration to the overall formula to see if there's mathematical cancellations.

        Small correction, 1e9! / (1e9-4)! would not be 1/(1*2*3*4) but 1/(999999997*999999998*999999999*1000000000)

        And you are probably thinking about the binomial coefficient, which is n! / ( k! * (n-k)! )

Re: Operations with Extremely Large Numbers (BigApprox)
by tye (Sage) on Nov 09, 2011 at 17:43 UTC

    If you want to use huge factorials for practical calculations related to the magnitude of the number, rather than as a demonstration of being able to exactly determine every digit of such a factorial, then a relatively fast way to do such things in Perl is Math::BigApprox.

    Computing 2032597! still takes a while because it actually does the 2 million multiplications and, although Math::BigApprox operations are about 10x faster than Math::BigInt operations (even when using GMP, same for bignum) and don't fail for this case, they are still about 20x slower than vanilla Perl math operations here.

    % perl -MMath::BigApprox=c -e'$s= time(); $f= !c(203259); print $f, $ +/, time()-$s," seconds\n"' 4.81043e+990637 14 seconds % perl -MMath::BigApprox=c -e'$s= time(); $f= !c(2032597); print $f, $ +/, time()-$s," seconds\n"' 1e+11938984 133 seconds

    Not an ideal solution, but perhaps an option you weren't aware of.

    - tye        

      Thanks for the interesting insight tye. Going along those lines, is it possible to memoize operations of BigApprox?

        Yes.

        use Math::BigApprox qw/Fact/; use Benchmark qw/cmpthese/; use Memoize; memoize( 'Fact', INSTALL => 'fastFact' ); our $test = 20000; print "$test!\t=>\t", Fact($test), "\n"; cmpthese ( -5, { Fact => sub{ my $a = Fact($test) }, fastFact => sub{ my $a = fastFact($test) }, } );

        Results...

        20000! => 1.819206e+77337 Rate Fact fastFact Fact 7.89/s -- -100% fastFact 297159/s 3766395% --

        You almost need Math::BigApprox to deal with the percent improvement when memoized. A factorial is one of those "pure functions" where memoization pays off if you intend to use the same params more than once.


        Dave

Re: Operations with Extremely Large Numbers
by mrstlee (Beadle) on Nov 09, 2011 at 17:39 UTC
    Two problems.
    1.
    sub factorial { my $n = @_;

    This forces the @_ array to be evaluated in a scalar context. An array evaluated in a scalar context produces the length of the array. Useful - but not what you want in this case!

    Change it to:
    my ($n) = @_;

    2.  my $exp = ($n/exp(1))^$n;
    '^' is the bitwise XOR operator in perl.

    Change to:
      my $exp = ($n/exp(1))**$n;

    Actually there may be a third problem. I kicked this off before writing this response. My (admittedly lame) PC has been maxed out since.

Re: Operations with Extremely Large Numbers
by JavaFan (Canon) on Nov 09, 2011 at 17:27 UTC
    perl -wE '@x = (1..2032597); $" = "*"; say "@x"' | bc
    and then go for a coffee.