One of the programming problems I have come across involves calculation of factorials of large numbers (of numbers up to 10^5). I have seen a simple Haskell code which goes lik
The difference is, as shachaf said, that GHC (by default) uses GMP for Integer
computations that exceed the Int
range, and GMP is rather well optimised. It has nothing to do with purity, caching, tail-call optimisation or such.
Java's BigInteger
uses more or less the naive schoolbook algorithms. If you look at the code for multiply (openjdk7), the work horse is
/**
* Multiplies int arrays x and y to the specified lengths and places
* the result into z. There will be no leading zeros in the resultant array.
*/
private int[] multiplyToLen(int[] x, int xlen, int[] y, int ylen, int[] z) {
int xstart = xlen - 1;
int ystart = ylen - 1;
if (z == null || z.length < (xlen+ ylen))
z = new int[xlen+ylen];
long carry = 0;
for (int j=ystart, k=ystart+1+xstart; j>=0; j--, k--) {
long product = (y[j] & LONG_MASK) *
(x[xstart] & LONG_MASK) + carry;
z[k] = (int)product;
carry = product >>> 32;
}
z[xstart] = (int)carry;
for (int i = xstart-1; i >= 0; i--) {
carry = 0;
for (int j=ystart, k=ystart+1+i; j>=0; j--, k--) {
long product = (y[j] & LONG_MASK) *
(x[i] & LONG_MASK) +
(z[k] & LONG_MASK) + carry;
z[k] = (int)product;
carry = product >>> 32;
}
z[i] = (int)carry;
}
return z;
}
a quadratic digit-by-digit multiplication (digits are of course not base 10). That doesn't hurt too much here, since one of the factors is always single-digit, but indicates that not too much work has yet been put into optimising BigInteger
computations in Java.
One thing that can be seen from the source is that in Java products of the form smallNumber * largeNumber
are faster than largeNumber * smallNumber
(in particular if the small number is single-digit, having that as the first number means the second loop with the nested loop doesn't run at all, so you have altogether less loop control overhead, and the loop that is run has a simpler body).
So changing
f = f.multiply(BigInteger.valueOf(i));
in your Java version to
f = BigInteger.valueOf(i).multiply(f);
gives a considerable speedup (increasing with the argument, ~2× for 25000, ~2.5× for 50000, ~2.8× for 100000).
The computation is still much slower than the GHC/GMP combination by a factor of roughly 4 in the tested range on my box, but, well, GMP's implementation is plain better optimised.
If you make computations that often multiply two large numbers, the algorithmic difference between the quadratic BigInteger
multiplication and GMP's that uses Karatsuba or Toom-Cook when the factors are large enough (FFT for really large numbers) would show.
However, if multiplying is not all that you do, if you print out the factorials, hence convert them to a String
, you get hit by the fact that BigInteger
's toString
method is abominably slow (it's roughly quadratic, so since the computation of the factorial is altogether quadratic in the length of the result, you get no [much] higher algorithmic complexity, but you get a big constant factor on top of the computation). The Show
instance for Integer
is much better, O(n * (log n)^x)
[not sure what x
is, between 1 and 2], so converting the result to String
adds just a little to the computation time.