Saturday, August 12, 2023

Nifty Info on 123456789 Factorial

The answers provided in the questions were collected using:

  • Default eclipse 2023-06 IDE for Mac
  • No special java vm args
  • jdk 1.8
  • Mac Studio M1 (10 core)

Question : How many digits are there in 123456789 factorial?

Answer : 260,959,261 digits.

Question : How long does it take to compute 123456789 factorial?

  • Answer 1 : Using prime sieve method, it takes 28,244,345 ms.
  • Answer 2 : Using recursive method, StackOverflowError.

Question : If the result of 123456789 factorial was written to a plain text file, how big will it be?

Answer : 263.6 MB.

Here's the full java code:

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class FactorialUsingPrimeSieveMethod {

    private int findExponent(int input, int prime) {
        int q = input, m = 0;
        if (prime > input) {
            return 0;
        }
        if (prime > input / 2) {
            return 1;
        }
        while (q >= prime) {
            q /= prime;
            m += q;
        }
        return m;
    }

    private boolean[] initializeSieve(int input) {
        boolean sieve[] = new boolean[input + 1];
        Arrays.fill(sieve, true);

        for (int p = 2; p * p <= input; p++) {
            if (sieve[p] == true) {
                for (int i = p * p; i <= input; i += p) {
                    sieve[i] = false;
                }
            }
        }

        return sieve;
    }

    private int[] selectPrimeNumbers(int input) {
        ArrayList<Integer> primeTable = new ArrayList<Integer>();
        boolean sieve[] = initializeSieve(input);
        for (int i = 2; i <= input; i++) {
            if (sieve[i] == true) {
                primeTable.add(i);
            }
        }

        return primeTable.stream().mapToInt(i -> i).toArray();

    }

    private int[] generateExponentTable(int input, int[] primeTable) {
        ArrayList<Integer> exponentTable = new ArrayList<Integer>();
        for (int prime : primeTable) {
            exponentTable.add(Integer.valueOf(findExponent(input, prime)));
        }

        return exponentTable.stream().mapToInt(i -> i).toArray();

    }

    private LinkedList<BigInteger> runParallelExponentiation(int[] primeTable, int[] exponentTable) {
        int proc = Runtime.getRuntime().availableProcessors();
        ExecutorService executorService = Executors.newFixedThreadPool(proc);

        List<Future<BigInteger>> futureList = new ArrayList<Future<BigInteger>>();
        LinkedList<BigInteger> intermediate = new LinkedList<BigInteger>();

        for (int i = 0; i < primeTable.length; i++) {
            Callable<BigInteger> worker = new PrimeSieveExponentiationWrapper(primeTable[i], exponentTable[i]);
            Future<BigInteger> submit = executorService.submit(worker);
            futureList.add(submit);
        }

        for (Future<BigInteger> future : futureList) {
            try {
                intermediate.add(future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }

        executorService.shutdown();
        return intermediate;
    }

    private synchronized BigInteger runParallelMultiplication(LinkedList<BigInteger> intermediate) {
        BigInteger one = BigInteger.ONE;
        int proc = Runtime.getRuntime().availableProcessors();
        ExecutorService executorService = Executors.newFixedThreadPool(proc);

        final LinkedList<BigInteger> roundaboutList = intermediate;

        while (roundaboutList.size() != 1) {
            System.out.println(" Current size = " + roundaboutList.size());
            BigInteger x = intermediate.pollLast();
            System.out.println(" Size after first poll = " + roundaboutList.size());
            BigInteger y = intermediate.pollLast();
            System.out.println(" Size after second poll = " + roundaboutList.size());
            Callable<BigInteger> worker = new PrimeSieveMultiplicationWrapper(x, y);
            Future<BigInteger> submit = executorService.submit(worker);
            try {
                roundaboutList.addFirst(submit.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }

        executorService.shutdown();
        return one.multiply(roundaboutList.getFirst());
    }

    private BigInteger factorial(int input) {
        BigInteger result = BigInteger.ONE;
        int[] primeTable = selectPrimeNumbers(input);
        int[] exponentTable = generateExponentTable(input, primeTable);

        final LinkedList<BigInteger> intermediate = runParallelExponentiation(primeTable, exponentTable);
        BigInteger product = runParallelMultiplication(intermediate);
        return result.multiply(product);
    }

    private void exportToFile(BigInteger result, int n) {
        String toParse = result.toString();
        String parsedResult = toParse.replaceAll("(.{100})", "$1\n");
        String path = System.getProperty("user.home") + File.separator + "results" + File.separator + String.valueOf(n)
                + ".txt";

        try {
            File fileName = new File(path);
            if (!fileName.exists()) {
                fileName.getParentFile().mkdirs();
                fileName.createNewFile();
            }
            FileWriter writer = new FileWriter(fileName);
            writer.write(parsedResult);
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void recordExecTime(FactorialUsingPrimeSieveMethod calculator, int n) {
        long startTime = System.currentTimeMillis();
        BigInteger result = calculator.factorial(n);
        long endTime = System.currentTimeMillis();
        System.out.println(n + "! took " + (endTime - startTime) + " ms using prime sieve method. Result has = "
                + result.toString().length() + " digits");
        //exportToFile(result, n);
    }

    public static void main(String[] args) {
        FactorialUsingPrimeSieveMethod calculator = new FactorialUsingPrimeSieveMethod();
        calculator.recordExecTime(calculator, 12345);
        calculator.recordExecTime(calculator, 123456);
        calculator.recordExecTime(calculator, 1234567);
        calculator.recordExecTime(calculator, 12345678);
        calculator.recordExecTime(calculator, 123456789);
    }

    class PrimeSieveExponentiationWrapper implements Callable<BigInteger> {

        int prime;
        int exponent;

        public PrimeSieveExponentiationWrapper(int prime, int exponent) {
            this.prime = prime;
            this.exponent = exponent;
        }

        @Override
        public BigInteger call() throws Exception {
            // System.out.println(" Calculating " + prime + " ^ " + exponent);
            return BigInteger.valueOf(prime).pow(exponent);
        }
    }

    class PrimeSieveMultiplicationWrapper implements Callable<BigInteger> {

        private BigInteger x;
        private BigInteger y;

        PrimeSieveMultiplicationWrapper(BigInteger x, BigInteger y) {
            this.x = x;
            this.y = y;
        }

        @Override
        public BigInteger call() throws Exception {
            if (x == null) {
                if (y == null) {
                    return BigInteger.ONE;
                } else {
                    return y;
                }
            }
            return x.multiply(y);
        }
    }
}