Logiciel Libre

August 20, 2005

Factorial Challenge: Python, Perl, Ruby, and C

Filed under: Default — adam @ 8:00 am UTC

While thumbing through a Ruby guide, I saw an example of computing factorials. Just out of curiosity, I decided to see how Ruby stacks up against C, Python, and Perl for this particular problem. Comments/suggestions/criticisms welcome. It would also be cool to see examples in other languages, so send ‘em in!

I “cheated” in Perl and C by using the GNU Multiple Precision Arithmetic Library. Just seemed easier that way. All code examples on this page are hereby released under the GPL.

The Code

Python

#!/usr/bin/python

# Computes factorial for number passed as first command
# line argument.

import sys

total = 0
def fact(n):
    total = n
    while n > 1:
        total *= (n - 1)
        n -= 1
    return total

print fact(int(sys.argv[1]))

Perl

#!/usr/bin/perl

# Computes factorial for number passed as first command
# line argument.

use Math::BigInt lib => 'GMP';
$b = Math::BigInt->new($ARGV[0]);
print $b->bfac(),"\n";

Ruby

#!/usr/bin/ruby

# Computes factorial for number passed as first command
# line argument.

def fact(n)
  if n == 0
    1
  else
    n * fact(n-1)
  end
end

print fact(ARGV[0].to_i), "\n"

C

/*
 * Computes factorial for number passed as first command
 * line argument.
 *
 * Uses the GNU Multiple Precision Arithmetic Library (see
 * http://www.swox.com/gmp/ ).
 *
 * The best "quick start" guide I could find was in the
 * INSTALL file in the gmp tarball.
 *
 * Compile with the -lgmp command line switch.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <gmp.h>

int
main (int argc, char **argv)
{
  mpz_t fact;

  if (argc != 2)
  {
    printf ("Usage: %s <number>\n", argv[0]);
    printf ("The factorial of <number> will be returned.\n");
    return EXIT_FAILURE;
  }

  mpz_init (fact);
  mpz_fac_ui (fact, atoi(argv[1]));
  gmp_printf ("%Zd\n", fact);

  return EXIT_SUCCESS;
}

The Results

Here are my one-off timings of each on my 1.8Ghz Pentium 4 desktop. To get the results, I just executed the code a bunch of times and took the best one.

$ time python/factorial/factorial.py 40
815915283247897734345611269596115894272000000000

real    0m0.039s
user    0m0.024s
sys     0m0.010s

$ time perl/factorial/factorial.pl 40
815915283247897734345611269596115894272000000000

real    0m0.164s
user    0m0.140s
sys     0m0.014s

$ time ruby/factorial/factorial.rb 40
815915283247897734345611269596115894272000000000

real    0m0.015s
user    0m0.005s
sys     0m0.007s

$ time c/factorial/factorial 40
815915283247897734345611269596115894272000000000

real    0m0.006s
user    0m0.001s
sys     0m0.002s

Of the scripting languages, Ruby led the pack. I used the recursive factorial example right out of that Ruby guide I mentioned earlier, so it has a limit of about 2,800! or so.

So, how ’bout 100,000!? Well, first I have to rewrite the Ruby solution to be non-recursive. Sorry if I’m not using “canonical” Ruby, this is the first time I’ve written any.

#!/usr/bin/ruby

# Computes factorial for number passed as first command
# line argument. Non-recursive (iterative) version.

total = 0
def fact(n)
  total = n
  while n > 1
    total *= (n - 1)
    n -= 1
  end
  return total
end

print fact(ARGV[0].to_i), "\n"

And here are some more quick-and-dirty benchmarks:

$ time python/factorial/factorial.py 100000 > /tmp/python_fact.txt

real    6m41.018s
user    5m14.327s
sys     0m18.453s

$ time perl/factorial/factorial.pl 100000 > /tmp/perl_fact.txt
<CTRL-C>
real    63m41.182s
user    48m54.874s
sys     0m12.881s

$ time ruby/factorial/nonrecursive.rb 100000 > /tmp/ruby_fact.txt

real    9m22.536s
user    6m51.273s
sys     0m19.430s

$ time c/factorial/factorial 100000 > /tmp/c_fact.txt

real    0m3.834s
user    0m3.769s
sys     0m0.023s

I got tired of waiting for the Perl script to complete. No idea how/why Python nosed out Ruby in this example. /tmp/python_fact.txt, /tmp/ruby_fact.txt, and /tmp/c_fact.txt turned out identical (as expected), but since I interrupted the Perl process, I never got a result.

Synopsis

Basically, C is fast (duh), but I was really surprised at Ruby’s speed. Python and Ruby do something cool: whenever a number gets too large, it’s automatically converted into a bignum-kind of object. I’m not sure I’m too into Ruby’s syntax.

Other Tidbits

Those interested in how to find large factorials might enjoy this Google answer on how to compute 1,000,000!

Interesting: apparently, Ruby can be translated into C.

For others interested in multiple-language programming, check out the ACM “Hello World” project, which shows “Hello, World!” programs in many different languages.

While I’m at it…

$ time c/factorial/factorial 1000000 > /dev/null 

real    2m32.795s
user    1m49.357s
sys     0m1.374s

Fast enough for me!

Update

Patrick rightly pointed out that Perl is not slow, and I completely agree. However, there is a loss of precision if Perl is left to handle numbers on its own. For example, the following implies something less than unlimited precision:

$ cat factorial.pl
#!/usr/bin/perl -w
use strict;

sub fact($) {
  my $total = my $n = shift;
  while ($n > 1) {
    $total *= ($n-- - 1);
  }
  return $total;
}

printf("%.0f\n", fact($ARGV[0]));
$ perl t.pl 40
815915283247898008314102179727920573516005507072

Speed gains in Perl might also be achieved by not use’ing strict and by disabling warnings.

I should be more specific on the requirements of the challenge, so here goes: compute factorials of numbers while supporting as much precision as possible.

8 Comments »

  1. Adam (whose weblog requires I log in to leave comments directly) notices that python and ruby’s performance seem great while C is (naturally) very fast. Perl is in the mix, too, and he kindly doesn’t tear it apart though based on the numbers he would seem justified in doing so. I wondered if perl was realy that slow so I decided to take a look…

    Comment by Patrick — August 22, 2005 @ 12:49 am UTC

  2. just for fun, the original ruby function can be replaced with:

    def fact(n)
      n == 0 ? 1 : n * fact(n-1)
    end
    

    Comment by pjh — September 27, 2005 @ 12:01 am UTC

  3. I just exec the factorial.py file

    import time
    start=time.clock()
    def f(n):return reduce(lambda x,y:x*y,range(1,n+1))
    f(n)
    print "the time is :",time.clock()-start

    in my 1.6G AMD(Duron) the result is:
    40!: 0.0004 (s)
    10000!: 0.22 (s)
    100000!: 202 (s)

    Comment by muyufan — October 1, 2006 @ 2:20 am UTC

  4. Here’s a version that’s a bit simpler and isn’t recursive:

    class Fixnum
        def factorial
            (2..self).inject(1) { |product, i| product*i }
        end
    end

    It’s a bit easier to use to because you can just call it this way:
    p 6.factorial

    Comment by Brian — December 16, 2006 @ 6:18 pm UTC

  5. Your Perl run was slow only because you didn’t actually use GMP library there: Perl “gracefully” fails to Perl-written implementation, when GMP is not installed. You should start your PPM (Perl Package Manager) and install Math::BigInt::GMP package. Then, Perl would be just as fast as C, for your starting example.

    Comment by acc — January 23, 2007 @ 9:24 am UTC

  6. Recursive version, equivalent to Ruby, is also fast, once you install the already mentioned package.

    #!/usr/bin/perl -w
    use strict;
    
    use Math::BigInt lib => 'GMP';
    
    use bignum;
    
    sub fact
    {
        my $n = $_[0];
        ( $n == 0 )
        ? 1
        : $n * fact( $n - 1 );
    }
    
    print fact( $ARGV[0] ), "\n"
    

    Comment by acc — January 23, 2007 @ 9:33 am UTC

  7. Hi, I’ve been desperately searching how to (in ruby) use the factorial in ruby, and as my friend only introduced me to ruby yesterday I was hoping you could explain to me, in dumbed down terms, how to use it. If not, could you tell me where to go to ask?
    thanks
    Andrew

    Comment by Help — November 10, 2008 @ 12:32 pm UTC

  8. You can get C speed in Python using “gmpy” module.

    import gmpy
    fact = gmpy.fac

    Comment by Miki — March 23, 2009 @ 4:53 pm UTC

RSS feed for comments on this post. TrackBack URL

Leave a comment

Powered by WordPress