Quick way to find a factorial of a large number

假装没事ソ 提交于 2019-12-07 12:13:03

问题


This is my program, but for really large numbers like 100,000, it works very slowly, is there any option to optimize?

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

public class Main {

    public static void main(String[] args) {

        Scanner in = new Scanner(System.in);

        int n = in.nextInt();

        BigInteger sum = BigInteger.valueOf(1);

        for (BigInteger i = BigInteger.valueOf(n);
             i.compareTo(BigInteger.ZERO) > 0;
             i = i.subtract(BigInteger.ONE)) {

            sum = sum.multiply(i);
        }

        System.out.println(sum);    
    }

}

回答1:


Just to illustrate that it sometimes pays to manipulate the expression, I modified the standard multiplication loop that computes 1*2*3*...*n to break it into two parts: one part multiplies the odd integers together (1*3*5*...) and the other multiplies the evens together (2*4*6*...). The evens product is further broken down by multiplying the evens that are 0 mod 2 but not 0 mod 4 (e.g. 2*6*10*...), then the evens that are 0 mod 4 but not 0 mod 8 (e.g. 4*12*20*28*...) and so on, but the power of 2 is shifted out of the number first. The powers of two are counted up, and the product is then shifted left all at once at the end. This takes advantage of the how the Java 8 BigInteger is implemented to make large left shifts fairly efficient.

private static BigInteger fac4(int n) {

    BigInteger evens = multiplyEvens(n);
    BigInteger odds = multiplyOdds(n);
    BigInteger product = evens.multiply(odds);
    return product;
}

private static BigInteger multiplyOdds(int n) {
    BigInteger odds = BigInteger.ONE;
    for (long i=1; i<=n; i+=2) {
        odds = odds.multiply(BigInteger.valueOf(i));
    }
    return odds;
}

private static BigInteger multiplyEvens(int n) {
    BigInteger evens = BigInteger.ONE;
    long pow2 = 1;
    int shiftAmount = 0;
    while ((1 << pow2) <= n) {
        for (long i = (1<<pow2); i <= n; i += (1 << (pow2 + 1))) {
            shiftAmount += pow2;
            evens = evens.multiply(BigInteger.valueOf(i >> pow2));
        }
        ++pow2;
    }
    return evens.shiftLeft(shiftAmount);
}


public static void main(String[] args) {
    // Print out some small factorials to verify things are working
    for (int i = 0; i < 10; i++) {
        System.out.printf("%d! = %d%n", i, fac4(i));
    }

    Scanner in = new Scanner(System.in);

    int n = in.nextInt();
    long start = System.currentTimeMillis();
    BigInteger fac = fac4(n);
    long end = System.currentTimeMillis();
    float total = end - start;

    System.out.printf("%d! is %d bits long, took %f seconds to compute", n, fac.bitLength(), total / 1000);
}

Here is the input/output log for one run of n=100000:

0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
100000
100000! is 1516705 bits long, took 1.758000 seconds to compute

For comparison, my implementation of the straightforward multiple loop took about 3 seconds.

EDIT:

Here is another implementation I tried that was even faster. The idea is to take advantage of the fact that Java 8+ BigInteger includes asymptotically faster than O(n2) algorithms when the operands of multiply get big enough to provide an advantage. However, the naive method always multiplies a single 'limb' integer by a rapidly growing accumulated product. This approach is not amenable to the faster algorithms. However, if we multiply approximately equal operands then the faster algorithms are possible.

private static final int SIMPLE_THRESHOLD = 10;
private static BigInteger fac6(int n) {
    return subfac(1, n);
}

/**
 * compute a * (a+1) * ... *(b-1) * b
 * The interval [a,b] includes the endpoints a and b.
 *
 * @param a the interval start.
 * @param b the interval end, inclusive.
 * @return the product.
 */
private static BigInteger subfac(int a, int b) {
    if ((b-a) < SIMPLE_THRESHOLD) {
        BigInteger result = BigInteger.ONE;
        for (int i=a; i<=b; i++) {
            result = result.multiply(BigInteger.valueOf(i));
        }
        return result;
    } else {
        int mid = a + (b-a) / 2;
        return subfac(a, mid).multiply(subfac(mid+1, b));
    }

}

And the output using the same main() method as above was:

0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
100000
100000! is 1516705 bits long, took 0.243000 seconds to compute

So fac6() is almost 10 times faster than fac4(). A few experiments suggest that the value of SIMPLE_THRESHOLD has very little effect on the speed, presumably because the overhead of function call is dwarfed by the cost of the BigInteger multiplication.

All these experiments were run on a Mac OS X High Sierra laptop using JDK 1.8.0_181.




回答2:


This is my first obvious implementation:

public static void main(String[] args) {
        long start = System.currentTimeMillis();
        int n = 100000;
        BigInteger bigInteger = BigInteger.ONE;
        for (int i = 1; i < n; i++) {
            bigInteger = bigInteger.multiply(BigInteger.valueOf(i));
        }
        System.out.println(bigInteger);
        long end = System.currentTimeMillis();
        float total = end - start;
        System.out.println(total);
    }

Factorial of 100000 is a number with 456569 digits (so I can't print it here), and my solution takes 3.5 seconds, more or less.

If that's not assumible for you, you must design a multi-thread based solution. For instance, one thread multiply first half of n and another thread does the same but for the second half. Then, multiply those two numbers.



来源:https://stackoverflow.com/questions/51445285/quick-way-to-find-a-factorial-of-a-large-number

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!