Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to calculate binomial coefficents for large numbers

I need to calculate n!/(n-r)!r! in C#. It's easy to calculate using the factorial function for small numbers but when the number gets bigger like 100, it doesn't work. Is there any other way with which we can calculate combinations for larger numbers?

like image 386
Jay Avatar asked Nov 29 '22 03:11

Jay


2 Answers

First off, I note that you are attempting to calculate the binomial coefficient, so let's call it that.

Here are a number of ways to do the calculation. If you use BigInteger you do not have to worry about overflow:

Method one: use factorial:

static BigInteger Factorial(BigInteger n)
{
    BigInteger f = 1;
    for (BigInteger i = 2; i <= n; ++i)
        f = f * i;
    return f;
}

static BigInteger BinomialCoefficient(BigInteger n, BigInteger k)
{
    return Factorial(n) / (Factorial(n-k) * Factorial(k));
}

Method two: use recursion:

static BigInteger BinomialCoefficient(BigInteger n, BigInteger k)
{
    if (n == 0) return 1;
    if (k == 0) return 0;
    return BinomialCoefficient(n-1, k-1) + BinomialCoefficient(n-1, k)
}

This method however is not fast unless you memoize the result.

Method Three: be more clever about minimizing the number of multiplications, and dividing early. This keeps the numbers small:

static BigInteger BinomialCoefficient(BigInteger n, BigInteger k)
{
    // (n C k) and (n C (n-k)) are the same, so pick the smaller as k:
    if (k > n - k) k = n - k;
    BigInteger result = 1;
    for (BigInteger i = 1; i <= k; ++i)
    {
        result *= n - k + i;
        result /= i;
    }
    return result;
}

So for example if you were computing (6 C 3), instead of computing (6 x 5 x 4 x 3 x 2 x 1) / ( (3 x 2 x 1) x (3 x 2 x 1)), you compute (((4 / 1) * 5) / 2) * 6) / 3, which keeps the numbers small if possible.

like image 113
Eric Lippert Avatar answered Dec 09 '22 20:12

Eric Lippert


Following what Eric said, dividing early helps a great deal, you can improve that by dividing from high to low. This way you can calculate any result as long as the endresult fits in a Long. Here's the code I use (apologize for the Java, but converting should be easy) :

public static long binomialCoefficient(int n, int k) {
   // take the lowest possible k to reduce computing using: n over k = n over (n-k)
   k = java.lang.Math.min( k, n - k );

   // holds the high number: fi. (1000 over 990) holds 991..1000
   long highnumber[] = new long[k];
   for (int i = 0; i < k; i++)
      highnumber[i] = n - i; // the high number first order is important
   // holds the dividers: fi. (1000 over 990) holds 2..10
   int dividers[] = new int[k - 1];
   for (int i = 0; i < k - 1; i++)
      dividers[i] = k - i;

   // for every divider there always exists a highnumber that can be divided by 
   // this, the number of highnumbers being a sequence that equals the number of 
   // dividers. Thus, the only trick needed is to divide in reverse order, so 
   // divide the highest divider first trying it on the highest highnumber first. 
   // That way you do not need to do any tricks with primes.
   for (int divider: dividers) 
      for (int i = 0; i < k; i++)
         if (highnumber[i] % divider == 0) {
            highnumber[i] /= divider;
            break;
         }

   // multiply remainder of highnumbers
   long result = 1;
   for (long high : highnumber)
      result *= high;
   return result;
}
like image 29
Jeroen Vuurens Avatar answered Dec 09 '22 21:12

Jeroen Vuurens