在Java中测试primality的最快方法是什么?

Ana*_*ran 50 java algorithm performance primes

我试图找到检查给定数字是否为素数的最快方法(在Java中).以下是我提出的几种素性测试方法.有没有比第二个实现更好的方法(isPrime2)?

    public class Prime {

        public static boolean isPrime1(int n) {
            if (n <= 1) {
                return false;
            }
            if (n == 2) {
                return true;
            }
            for (int i = 2; i <= Math.sqrt(n) + 1; i++) {
                if (n % i == 0) {
                    return false;
                }
            }
            return true;
        }
        public static boolean isPrime2(int n) {
            if (n <= 1) {
                return false;
            }
            if (n == 2) {
                return true;
            }
            if (n % 2 == 0) {
                return false;
            }
            for (int i = 3; i <= Math.sqrt(n) + 1; i = i + 2) {
                if (n % i == 0) {
                    return false;
                }
            }
            return true;
        }
    }



public class PrimeTest {

    public PrimeTest() {
    }

    @Test
    public void testIsPrime() throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {

        Prime prime = new Prime();
        TreeMap<Long, String> methodMap = new TreeMap<Long, String>();


        for (Method method : Prime.class.getDeclaredMethods()) {

            long startTime = System.currentTimeMillis();

            int primeCount = 0;
            for (int i = 0; i < 1000000; i++) {
                if ((Boolean) method.invoke(prime, i)) {
                    primeCount++;
                }
            }

            long endTime = System.currentTimeMillis();

            Assert.assertEquals(method.getName() + " failed ", 78498, primeCount);
            methodMap.put(endTime - startTime, method.getName());
        }


        for (Entry<Long, String> entry : methodMap.entrySet()) {
            System.out.println(entry.getValue() + " " + entry.getKey() + " Milli seconds ");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Bar*_*ers 70

这是另一种方式:

boolean isPrime(long n) {
    if(n < 2) return false;
    if(n == 2 || n == 3) return true;
    if(n%2 == 0 || n%3 == 0) return false;
    long sqrtN = (long)Math.sqrt(n)+1;
    for(long i = 6L; i <= sqrtN; i += 6) {
        if(n%(i-1) == 0 || n%(i+1) == 0) return false;
    }
    return true;
}
Run Code Online (Sandbox Code Playgroud)

并且BigInteger's isProbablePrime(...)对所有32位有效int.

编辑

请注意,isProbablePrime(certainty)并不总是产生正确的答案.当确定性偏低时,会产生误报,如评论中提到的@ dimo414.

不幸的是,我找不到声称isProbablePrime(certainty)对所有(32位)有效的来源int(给定足够的确定性!).

所以我进行了几次测试.我创建了一个代表所有不均匀数字BitSet的大小,Integer.MAX_VALUE/2并使用主筛来查找范围内的所有素数1..Integer.MAX_VALUE.然后我循环i=1..Integer.MAX_VALUE测试每一个new BigInteger(String.valueOf(i)).isProbablePrime(certainty) == isPrime(i).

对于确定性5和10,isProbablePrime(...)沿线产生误报.但是isProbablePrime(15),没有测试失败.

这是我的测试台:

import java.math.BigInteger;
import java.util.BitSet;

public class Main {

    static BitSet primes;

    static boolean isPrime(int p) {
        return p > 0 && (p == 2 || (p%2 != 0 && primes.get(p/2)));
    }

    static void generatePrimesUpTo(int n) {
        primes = new BitSet(n/2);

        for(int i = 0; i < primes.size(); i++) {
            primes.set(i, true);
        }

        primes.set(0, false);
        int stop = (int)Math.sqrt(n) + 1;
        int percentageDone = 0, previousPercentageDone = 0;
        System.out.println("generating primes...");
        long start = System.currentTimeMillis();

        for(int i = 0; i <= stop; i++) {
            previousPercentageDone = percentageDone;
            percentageDone = (int)((i + 1.0) / (stop / 100.0));

            if(percentageDone <= 100 && percentageDone != previousPercentageDone) {
                System.out.println(percentageDone + "%");
            }

            if(primes.get(i)) {
                int number = (i * 2) + 1;

                for(int p = number * 2; p < n; p += number) {
                    if(p < 0) break; // overflow
                    if(p%2 == 0) continue;
                    primes.set(p/2, false);
                }
            }
        }
        long elapsed = System.currentTimeMillis() - start;
        System.out.println("finished generating primes ~" + (elapsed/1000) + " seconds");
    }

    private static void test(final int certainty, final int n) {
        int percentageDone = 0, previousPercentageDone = 0;
        long start = System.currentTimeMillis();
        System.out.println("testing isProbablePrime(" + certainty + ") from 1 to " + n);
        for(int i = 1; i < n; i++) {
            previousPercentageDone = percentageDone;
            percentageDone = (int)((i + 1.0) / (n / 100.0));
            if(percentageDone <= 100 && percentageDone != previousPercentageDone) {
                System.out.println(percentageDone + "%");
            }
            BigInteger bigInt = new BigInteger(String.valueOf(i));
            boolean bigIntSays = bigInt.isProbablePrime(certainty);
            if(isPrime(i) != bigIntSays) {
                System.out.println("ERROR: isProbablePrime(" + certainty + ") returns "
                    + bigIntSays + " for i=" + i + " while it " + (isPrime(i) ? "is" : "isn't" ) +
                    " a prime");
                return;
            }
        }
        long elapsed = System.currentTimeMillis() - start;
        System.out.println("finished testing in ~" + ((elapsed/1000)/60) +
                " minutes, no false positive or false negative found for isProbablePrime(" + certainty + ")");
    }

    public static void main(String[] args) {
        int certainty = Integer.parseInt(args[0]);
        int n = Integer.MAX_VALUE;
        generatePrimesUpTo(n);
        test(certainty, n);
    }
}
Run Code Online (Sandbox Code Playgroud)

我跑的是这样做的:

java -Xmx1024m -cp . Main 15
Run Code Online (Sandbox Code Playgroud)

在我的机器上生成素数大约需要30秒.而所有的实际测试i1..Integer.MAX_VALUE花了大约2小时15分钟.

  • 由于这是java isprime搜索的第一个结果,我认为重点突出这个答案中的一个缺陷.每一个人都可以得到一个错误的答案.这是因为isProbablePrime使用Random实例来选择见证(并根据数字的长度,甚至覆盖确定性).示例:http://ideone.com/t3lo9G (3认同)
  • 您对BigInteger的看法有​​来源或证据吗?你有什么确定的用途?我已经看到isProbablePrime(1)以数字9失败,所以在你的答案中暗示它是/ always/valid显然是错误的,但是你可以肯定地确定int /是prime /?怎么样? (2认同)

use*_*008 43

这是最优雅的方式:

public static boolean isPrime(int n) {
    return !new String(new char[n]).matches(".?|(..+?)\\1+");
}
Run Code Online (Sandbox Code Playgroud)

Java 1.4+.不需要进口.

这么短.如此美丽.

  • 如果"优雅"的意思是"聪明和简洁",那么当然.如果"优雅"意味着"可读",我会说不.我当然不希望在代码中遇到这种情况. (32认同)
  • @anula比最简单的算法慢几万倍 (17认同)
  • 这没有什么优雅的. (16认同)
  • 这个正则表达式基本上是在一元中对一个数字进行试验分割.Perl已多次提及它; 您可以在许多网站上看到它的解释,例如http://stackoverflow.com/questions/3329766/ http://www.noulakaz.net/weblog/2007/03/18/a-regular-expression-to-check-for -prime-numbers/Java中唯一的区别是1)`.matches()`匹配整个字符串,因此你不需要`^`和`$`,和2)而不是重复`1`s(在Java中很难),我创建了一个包含所有空字符的字符串(通过创建一个带有新`char`数组的字符串),然后将它们与`.`匹配. (4认同)
  • @ ghostsoldier23:`.`匹配任何字符.`+`匹配前一个事件的一次或多次出现.`?`可能意味着先前的0或1; 当在`*`或`+`之后使用时,它也意味着"非贪婪"的匹配.并且```表示OR,所以左或右.`\ 1`是一个反向引用,即重复第一组中匹配的内容.基本上,`.. +`用于匹配2个或更多字符(`.. +?`只是让它非贪婪); 和`(上面的东西)\\ 1 +`用于匹配上面的东西的2次或更多次重复,因此它出现(数字2或更大)x(数字2或更大)(非素数)#字符# (3认同)
  • 正则表达式本质上相当于正整数系列的除法,这是"最坏情况"的"天真"解决方案,用于确定数字是否为素数. (3认同)

Bra*_*lor 12

看看AKS素性测试(及其各种优化).它是在多项式时间内运行的确定性素性测试.

这里有来自Tuebingen大学(德国)的 Java算法的实现

  • 维基百科:"虽然该算法具有极大的理论重要性**,但实际上并没有使用**.对于64位输入,Baillie-PSW是确定性的,并且运行速度快了许多个数量级.对于更大的输入,(无条件正确的)ECPP和APR测试的性能远远优于AKS." 这是在O(n)的定义中省略*乘法常数*的实际结果. (6认同)
  • 甚至链接的实现也说“因此,AkS 测试只对计算复杂性理论感兴趣。测试 2^13-1 需要大约 30 分钟才能有效实现。” *30 分钟测试数字 8191*。这是一些非常缓慢的测试。有更快的 AKS 版本,但它仍然不是这个问题的好答案。 (2认同)

Jim*_*mmy 10

你迈出了消除2的所有倍数的第一步.

但是,你为什么要止步呢?你可以消除所有3的倍数,除了3,所有5的倍数除了5,等等.

当你按照这个推理得出结论时,你会得到Eratosthenes筛子.

  • 你不是指权力,你的意思是倍数. (2认同)

Ari*_*lam 7

我认为这个方法是最好的。至少对于我来说-

    public static boolean isPrime(int num)
    {
        for (int i = 2; i<= num/i; i++)
        {
            if (num % i == 0)
            {
                return false;
            }
        }
        return num > 1;
    }
Run Code Online (Sandbox Code Playgroud)


kgi*_*kis 5

对于相当小的数字,您的算法将很好地工作。对于大数字,应使用高级算法(例如基于椭圆曲线)。另一个想法是使用一些“伪素数”测试。这些将快速测试一个数字是否是素数,但它们并不是 100% 准确。但是,它们可以帮助您比使用算法更快地排除某些数字。

最后,尽管编译器可能会为您优化这一点,但您应该编写:

int max =  (int) (Math.sqrt(n) + 1);
for (int i = 3; i <= max; i = i + 2) {
}
Run Code Online (Sandbox Code Playgroud)


Cha*_*les 5

Jaeschke (1993) 提出的快速测试是 Miller-Rabin 测试的确定性版本,它没有低于 4,759,123,141 的误报,因此可以应用于 Java int

// Given a positive number n, find the largest number m such
// that 2^m divides n.
private static int val2(int n) {
  int m = 0;
  if ((n&0xffff) == 0) {
    n >>= 16;
    m += 16;
  }
  if ((n&0xff) == 0) {
    n >>= 8;
    m += 8;
  }
  if ((n&0xf) == 0) {
    n >>= 4;
    m += 4;
  }
  if ((n&0x3) == 0) {
    n >>= 2;
    m += 2;
  }
  if (n > 1) {
    m++;
  }
  return m;
}

// For convenience, handle modular exponentiation via BigInteger.
private static int modPow(int base, int exponent, int m) {
  BigInteger bigB = BigInteger.valueOf(base);
  BigInteger bigE = BigInteger.valueOf(exponent);
  BigInteger bigM = BigInteger.valueOf(m);
  BigInteger bigR = bigB.modPow(bigE, bigM);
  return bigR.intValue();
}

// Basic implementation.
private static boolean isStrongProbablePrime(int n, int base) {
  int s = val2(n-1);
  int d = modPow(base, n>>s, n);
  if (d == 1) {
    return true;
  }
  for (int i = 1; i < s; i++) {
    if (d+1 == n) {
      return true;
    }
    d = d*d % n;
  }
  return d+1 == n;
}

public static boolean isPrime(int n) {
  if ((n&1) == 0) {
    return n == 2;
  }
  if (n < 9) {
    return n > 1;
  }

  return isStrongProbablePrime(n, 2) && isStrongProbablePrime(n, 7) && isStrongProbablePrime(n, 61);
}
Run Code Online (Sandbox Code Playgroud)

这不适用于long变量,但有一个不同的测试:BPSW 测试没有高达 2^64 的反例。这基本上包括一个像上面这样的 2-strong probable prime test,然后是一个更复杂但没有根本区别的强 Lucas 测试。

这两项测试都比任何类型的试验部门都要快得多。