对于大数字,素数因子分解算法失败

时间:2011-05-16 06:04:44

标签: java

对于Project Euler的问题3,我遇到了一个奇怪的问题。该程序适用于其他小的数字,如13195,但当我试图处理像600851475143这样的大数字时会抛出此错误:

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at euler3.Euler3.main(Euler3.java:16)

这是我的代码:

    //Number whose prime factors will be determined
    long num = 600851475143L;

    //Declaration of variables
    ArrayList factorsList = new ArrayList();
    ArrayList primeFactorsList = new ArrayList();

    //Generates a list of factors
    for (int i = 2; i < num; i++)
    {
        if (num % i == 0)
        {
            factorsList.add(i);
        }           
    }

    //If the integer(s) in the factorsList are divisable by any number between 1 
    //and the integer itself (non-inclusive), it gets replaced by a zero 
    for (int i = 0; i < factorsList.size(); i++)
    {
        for (int j = 2; j < (Integer) factorsList.get(i); j++)
        {
            if ((Integer) factorsList.get(i) % j == 0)
            {
                factorsList.set(i, 0);
            }
        }
    }

    //Transfers all non-zero numbers into a new list called primeFactorsList
    for (int i = 0; i < factorsList.size(); i++)
    {
        if ((Integer) factorsList.get(i) != 0)
        {
            primeFactorsList.add(factorsList.get(i));
        }
    }

为什么只有大数字会导致此错误?

4 个答案:

答案 0 :(得分:5)

您的代码只使用Integer,这是一个32位类型,最大值为2147483647.当用于比这大得多的数字时,它的失败并不令人惊讶。请注意,您的初始循环使用int作为循环变量,因此如果它没有抛出异常,它将实际循环。 i的值将从2147483647变为-2147483648并继续。

使用BigInteger处理任意大的值,或Long如果您对有限范围但更大的值感到满意。 (long / Long的最大值为9223372036854775807L。)

但是,我怀疑这是真正的预期方法......对于像这样的大数字来说,它需要时间。

答案 1 :(得分:1)

除了Jon Skeet提到的BigInteger问题外,请注意以下事项:

  1. 您只需要测试最多sqrt(num)
  2. 的因子
  3. 每次找到一个因子时,将num除以该因子,然后再次测试该因子
  4. 我的解决方案(最初用Perl编写)在Java中看起来像这样:

    long n = 600851475143L;          // the original input
    long s = (long)Math.sqrt(n);     // no need to test numbers larger than this
    long f = 2;                      // the smallest factor to test
    do {
        if (n % f == 0) {            // check we have a factor
             n /= f;                 // this is our new number to test
             s = (long)Math.sqrt(n); // and our range is smaller again
        } else {                     // find next possible divisor
             f = (f == 2) ? 3 : f + 2;
        }
    } while (f < s);                 // required result is in "n"
    

答案 2 :(得分:1)

不确定是否是这种情况,因为我不知道哪一行是哪个 - 但我注意到你的第一个循环使用了一个int。

//Generates a list of factors
for (int i = 2; i < num; i++)
{
    if (num % i == 0)
    {
        factorsList.add(i);
    }           
}

由于num很长,因此可能num > Integer.MAX_INT和你的循环在MAX_INT回绕到负数,然后循环到0,给你一个num % 0操作。

答案 3 :(得分:1)

为什么您的解决方案不起作用?

井号在硬件中是离散的。离散意味着你有最小值和最大值。 Java使用two's complement来存储负值,因此2147483647+1 == -2147483648。这是因为对于int类型,最大值为2147483647。这样做叫做溢出。

好像你有一个overflow bug。可变值i首先变为负数,最终变为0,因此您得到java.lang.ArithmeticException: / by zero。如果你的计算机每秒可以循环1000万个语句,这将需要 1h 10min 来重现,所以我把它留作假设而不是证据。

这也是像a+b这样的简单陈述可能会产生错误的原因。

如何解决?

package margusmartseppcode.From_1_to_9;

public class Problem_3 {
    static long lpf(long nr) {
        long max = 0;

        for (long i = 2; i <= nr / i; i++)
            while (nr % i == 0) {
                max = i;
                nr = nr / i;
            }

        return nr > 1 ? nr : max;
    }

    public static void main(String[] args) {
        System.out.println(lpf(600851475143L));
    }
}

您可能会想:“那么这是如何运作的?”

我的艰难过程就像:

  1. (动态编程方法)如果我有素数列表 x {2,3,5,7,11,13,17, ...}最多值 x i &gt; nr / 2 ,然后找到最大的素因子是微不足道的:
    • 我从最大的素数开始,并开始测试如果我的号码的删除提醒为零,如果是,那么这就是答案。
    • 如果在循环所有元素之后,我没有找到答案,我的号码本身必须是素数。
  2. (暴力,有过滤器)我假设,那
    • 我的数字最大的素数因素很小(低于1000万)。
    • 如果我的数字是某个数字的倍数,那么我可以减少该数量的循环大小。
  3. 我在这里使用了第二种方法。

    但是请注意,如果我的号码只是一点点而且只有{600851475013, 600851475053, 600851475067, 600851475149, 600851475151}之一,那么我的方法假设就会失败,程序将花费很长的时间来运行。如果计算机每秒可执行10米语句,则需要 6.954天才能找到正确的答案。

    在您的暴力方法中,只需生成一系列因素就会花费更长的时间 - 假设您之前没有耗尽内存。

    有更好的方法吗?

    当然,在Mathematica中你可以把它写成:

    P3[x_] := FactorInteger[x][[-1, 1]]
    P3[600851475143]
    

    或仅FactorInteger[600851475143],并查找最大值。

    这是有效的,因为在Mathematica中你有任意大小的整数。 Java还有一个名为BigInteger的任意大小的整数类。