查找从 2 到 1000 的所有素数的算法不起作用

Algorithm to find all primes from 2 to 1000 not working

这是一段代码,使用语句计算从 2 到 1000 的所有素数,数字 n 是素数当且仅当:

在第一个版本中,我认为我正确地实现了算法:

public class Giuga {

    public static void main(String[] args){
        int n = 2;

        while(n<=1000){
            int k = 1;
            long sum = 0;
            while(k<=n-1){
                sum = sum+(long)Math.pow((double)k,(double)n-1);
                k++;
            }
            if(sum%n==n-1){
                System.out.println(n + " is a prime.");
            }
            n++;
        }
    }
}

但是,由于变量sum增长很快,发生溢出,素数17之后就没有输出了。

为了防止我必须使用这个:

嗯,我做到了,这是我的 2. 版本:

public class Giuga {

    public static void main(String[] args){
        int n = 2;

        while(n<=1000){
            int k = 1;
            long sum = 0;
            while(k<=n-1){
                sum = sum+((long)Math.pow((double)k%n,(double)n-1))%n; //Here are the changes
                k++;
            }
            if(sum%n==n-1){
                System.out.println(n + " is a prime.");
            }
            n++;
        }
    }
}

我想我做对了,但是现在输出在素数 13 之后停止了。

我已经尝试找出我的错误已经有一段时间了。我究竟做错了什么? 从2到1000必须有168个素数

Java doubles(即 pow 的输出)不能精确地表示大数以产生正确的余数。你应该切换到 modular exponentiation.

如前所述,doubles 的精度只有大约 16 位,不够精确,无法为足够高的数字计算保持正确的余数。

您可以切换到 longs 并执行您自己的模幂运算。

int k = 1;
long sum = 0;
while(k<=n-1){
    long pow = 1;
    for (int i = 0; i < n - 1; i++)
        pow = (pow * k) % n;
    sum = (sum + pow)%n;
    k++;
}

可以通过将这种简单的模幂更改为通过重复平方使用模幂来改进该算法,它不是最有效的素数查找算法,但现在是正确的。

2 is a prime.
3 is a prime.
5 is a prime.
7 is a prime.
11 is a prime.
13 is a prime.
17 is a prime.
19 is a prime.
23 is a prime.
29 is a prime.
31 is a prime.

(剪断)

977 is a prime.
983 is a prime.
991 is a prime.
997 is a prime.

要通过重复平方使其模幂,替换

long pow = 1;
for (int i = 0; i < n - 1; i++)
    pow = (pow * k) % n;

long pow = 1;
long square = k;
int exp = n - 1;
while (exp > 0)
{
    if ((exp & 1) == 1)
    {
        pow = (pow * square) % n;
    }
    square = (square * square) % n;
    exp >>= 1;
}

依次测试指数的每一位,如果已设置,则将当前平方乘以pow

你的权力也太大了(猜猜999^999是多少)。因此,您还必须使用逐步 (ab) mod n = ((a mod n)(b mod n)) mod n 模幂 )来计算幂:

public class Giuga {

    public static void main(String[] args) {
        int count = 0;
        for (int i = 2; i < 1000; i++) {
            if (isPrime(i)) {
                count++;
                System.out.printf("%d is prime\n", i);
            }
        }
        System.out.printf("Found %d primes.\n", count);
    }

    // checks if number is a prime
    private static boolean isPrime(int number) {
        int bigSum = 0;
        for (int k = 1; k <= number - 1; k++) {
            bigSum = (bigSum + calcPowMod(k, number - 1, number)) % number;
        }
        return bigSum % number == number - 1;
    }

    // calculates (a^b)%mod, making sure that intermediate results are always < max(a^2,mod)
    private static int calcPowMod(int a, int b, int mod) {
        int pow = 1;
        for (int k = 1; k <= b; k++) {
            pow = (pow * a) % mod;
        }
        return pow;
    }
}

您始终可以使用 BigInteger class 进行大量计算

private static boolean isPrime(int n) {
    BigInteger N = new BigInteger(String.valueOf(n));
    BigInteger N_MINUS_1 = N.subtract(BigInteger.ONE); 

    BigInteger sum = BigInteger.ZERO;
    for (int k = 1;  k < n; k++)
        sum = sum.add(new BigInteger(String.valueOf(k)).modPow(N_MINUS_1,N)).mod(N);
    return sum.equals(N_MINUS_1);
}

有趣的是 这是 Fermat's little theorem 的变体,对于总和 Σ

中的每个 k

k^(n-1)%n应该是1,否则这个数不是质数!所以,如果我们找到一个 k 使得 k^(n-1)%n != 1,我们就可以停止计算。上述算法可以改写为:

private static boolean isPrimeFermat(int n) {
    BigInteger N = new BigInteger(String.valueOf(n));
    BigInteger N_MINUS_1 = N.subtract(BigInteger.ONE); 

    for (int k = 1;  k < n; k++){
        if (new BigInteger(String.valueOf(k)).modPow(N_MINUS_1, N).equals(BigInteger.ONE) == false)
            return false;
    }       
    return true;
}

瞧!