javaperformancebigintegerfactorialcatalan

Increasing performance when doing calculations with huge numbers (BigInteger)


I am a less experienced coder doing an exercise where I need to calculate the product of two catalan sequences for every single n-value between 0 and 5000 and then summarize those products.

The code currently outputs the correct answer but takes between 2.9-3.3 seconds to run with an n-value of 5000. My goal is to get the code to run in under 3 seconds every single time so I need to gain about half a second.

The largest number in the calculation (10,000!) is over 35,000 digits long so int or long can't be used for any of the heavier calculations, nor can I use any external libraries, which pretty much leaves me with BigInteger.

From testing I have discovered that the for-loop in sum()shown below is what takes the longest to complete by far (~85% of the run time) so that's where a performance increase is probably needed the most. Any tips on how to optimize it are appreciated.

// For all n-values
for (int k=0; k < n/2 + rest; k++) {
    result = result.add(catalan(k).multiply(catalan(n-k)));
}

Here is the entire code:

import java.math.BigInteger;
import java.util.Scanner;

public class FactorialSum {

    static BigInteger[] bigInt;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        try {
            int n = sc.nextInt();

            // Creates a new array and initializes the default values
            bigInt = new BigInteger[n*2+1];
            bigInt[0] = BigInteger.ONE;
            if (n > 0)
                bigInt[1] = BigInteger.ONE;

            calcFactorials(n);

            // Calculates and prints the results
            System.out.println(sum(n));
        } finally {
            sc.close();
        }
    }

    // Calculates and stores all the factorials up to and including the specified n-value
    private static void calcFactorials(int n) {
        for (int factor = 2; factor <= n*2; factor++) {
            bigInt[factor] = bigInt[factor-1].multiply(BigInteger.valueOf(factor));
        }
    }

    // Calculates the catalan number using the binomial coefficient for the
    // specified n-value
    private static BigInteger catalan(int n) {
        BigInteger binomial = bigInt[n*2].divide(bigInt[n].pow(2));
        BigInteger cn = binomial.divide(BigInteger.valueOf(n+1));
        return cn;
    }

    // Calculates the sum for the specified range 0-n
    private static BigInteger sum(int n) {
        if (n > 0) {
            BigInteger result = BigInteger.ZERO;
            int rest = n % 2;

            // For all n-values
            for (int k=0; k < n/2 + rest; k++) {
                result = result.add(catalan(k).multiply(catalan(n-k)));
            }
            result = result.multiply(BigInteger.valueOf(2));

            // For even n-values
            if (rest == 0) {
                BigInteger lastNumber = catalan(n/2);
                result = result.add(lastNumber.pow(2));
            }
            return result;
        } else {
            return BigInteger.ONE;
        }
    }
}

Solution

  • I need to calculate the product of two catalan sequences for every single n-value between 0 and 5000 and then summarize those products.

    Well, this is exactly an alternative definition of a Catalan number.

    Cn+1 = SUMi=0..n ( Ci * Cn-i )

    So, what you basically need is to calculate C5001. To calculate it fast, you may use another recurrence relation:

    Cn+1 = 2*(2n+1) / (n+2) * Cn

    Here is the program:

    public static void main(String[] args) {
        int n = 5000;
    
        BigInteger Cn = BigInteger.ONE;
        for (int i = 0; i <= n; i++) {
            Cn = Cn.multiply(BigInteger.valueOf(4 * i + 2)).divide(BigInteger.valueOf(i + 2));
        }
    
        System.out.println(Cn);
    }
    

    Works less than 0.04 sec on my laptop. Enjoy!