【问题标题】:What would be the fastest method to test for primality in Java?在 Java 中测试素数的最快方法是什么?
【发布时间】:2011-01-24 01:29:04
【问题描述】:

我正在尝试找到检查给定数字是否为素数的最快方法(在 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 ");
        }
    }
}

【问题讨论】:

  • 如果您需要知道这个数字是 100% 质数,那么您的解决方案是最好的。
  • 我认为您的解决方案会很好。您可能会散列结果,因此您只需要“计算”一次。为什么要使用反射来执行测试?
  • @Stefan Hendriks 向类添加一个方法,触发测试,你会得到排序结果(我很懒)。
  • JUnit @Test 注解 FTW ;)
  • @SimonT:问题在于a/4a&gt;&gt;2 不同,因为负数向上取整而不是向下取整。除非编译器可以证明a&gt;=0,否则它必须生成一个相当复杂的表达式以避免除法(仍然是一个改进,但类似于 3 个周期而不是单个指令)。

标签: java performance algorithm primes


【解决方案1】:

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

// 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);
}

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

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

【讨论】:

    【解决方案2】:

    一般来说,所有大于某个Primorial 整数C 的素数的形式为Ck+i for i &lt; C,其中ik 是整数,i 表示与C

    这是一个 C=30 的示例,它应该比 Bart Kiers 回答 C=6 的速度更快,您可以通过计算 C=210 来改进它

    boolean isPrime(long n) {
        if(n < 2){
            return false;
        }
        if(n == 2 || n == 3 || n == 5 || n == 7 || n == 11 || n == 13 || n == 17 || n == 19 || n == 23 || n == 29){
            return true;
        }
        
        long sqrtN = (long) Math.sqrt(n) + 1;
        int[] mods = {1, 7, 11, 13, 17, 19, 23, 29};
        for (long i = 30L; i <= sqrtN; i += 30) {
            for (int mod : mods) {
                if(n % (i + mod) == 0){
                    return false;
                }
            }
        }
        return true;
    }
    

    【讨论】:

      【解决方案3】:

      首先,素数从 2 开始。2 和 3 是素数。素数不能被 2 或 3 整除。其余素数的形式为 6k-1 和 6k+1。请注意,您应该检查直到 SQRT(input) 的数字。这种方法非常有效。希望对你有帮助。

      public class Prime {
      
          public static void main(String[] args) {
              System.out.format("%d is prime: %s.\n", 199, isPrime(199)); // Prime
              System.out.format("%d is prime: %s.\n", 198, isPrime(198)); // Not prime
              System.out.format("%d is prime: %s.\n", 104729, isPrime(104729)); // Prime
              System.out.format("%d is prime: %s.\n", 104727, isPrime(982443529)); // Prime
          }
      
          /**
           * Tells if a number is prime or not.
           *
           * @param input the input
           * @return If the input is prime or not
           */
          private boolean isPrime(long input) {
          if (input <= 1) return false; // Primes start from 2
          if (input <= 3) return true; // 2 and 3 are primes
          if (input % 2 == 0 || input % 3 == 0) return false; // Not prime if dividable by 2 or 3
          // The rest of the primes are in the shape of 6k-1 and 6k+1
          for (long i = 5; i <= Math.sqrt(input); i += 6) if (input % i == 0 || input % (i + 2) == 0) return false;
          return true;
          }
      
      }
      

      【讨论】:

        【解决方案4】:

        在 Intel Atom @ 1.60GHz、2GB RAM、32 位操作系统中测试

        测试结果:
        Long.MAX_VALUE=9223372036854775807 以下的最大素数是 9223372036854775783
        经过的时间是 171499 毫秒或 2 分 51 秒

        public class PrimalityTest
        {
            public static void main(String[] args)
            {
                long current_local_time = System.currentTimeMillis();
                long long_number = 9223372036854775783L;
                long long_a;
                long long_b;
                if (long_number < 2)
                {
                    System.out.println(long_number + " is not a prime number");
                }
                else if (long_number < 4)
                {
                    System.out.println(long_number + " is a prime number");
                }
                else if (long_number % 2 == 0)
                {
                    System.out.println(long_number + " is not a prime number and is divisible by 2");
                }
                else
                {
                    long_a = (long) (Math.ceil(Math.sqrt(long_number)));
                    terminate_loop:
                    {
                        for (long_b = 3; long_b <= long_a; long_b += 2)
                        {
                            if (long_number % long_b == 0)
                            {
                                System.out.println(long_number + " is not a prime number and is divisible by " + long_b);
                                break terminate_loop;
                            }
                        }
                        System.out.println(long_number + " is a prime number");
                    }
                }
                System.out.println("elapsed time: " + (System.currentTimeMillis() - current_local_time) + " millisecond/s");
            }
        }
        

        【讨论】:

          【解决方案5】:

          算法效率:O(n^(1/2)) 算法

          注意:下面的示例代码包含计数变量和对打印函数的调用以打印结果:

          import java.util.*;
          
          class Primality{
              private static void printStats(int count, int n, boolean isPrime) {
          
                  System.err.println( "Performed " + count + " checks, determined " + n
                  + ( (isPrime) ? " is PRIME." : " is NOT PRIME." ) );
              }
              /**
              *   Improved O( n^(1/2)) ) Algorithm
              *    Checks if n is divisible by 2 or any odd number from 3 to sqrt(n).
              *    The only way to improve on this is to check if n is divisible by 
              *   all KNOWN PRIMES from 2 to sqrt(n).
              *
              *   @param n An integer to be checked for primality.
              *   @return true if n is prime, false if n is not prime.
              **/
              public static boolean primeBest(int n){
                  int count = 0;
                  // check lower boundaries on primality
                  if( n == 2 ){ 
                      printStats(++count, n, true);
                      return true;
                  } // 1 is not prime, even numbers > 2 are not prime
                  else if( n == 1 || (n & 1) == 0){
                      printStats(++count, n, false);
                      return false;
                  }
          
                  double sqrtN = Math.sqrt(n);
                  // Check for primality using odd numbers from 3 to sqrt(n)
                  for(int i = 3; i <= sqrtN; i += 2){
                      count++;
                      // n is not prime if it is evenly divisible by some 'i' in this range
                      if( n % i == 0 ){ 
                          printStats(++count, n, false);
                          return false;
                      }
                  }
                  // n is prime
                  printStats(++count, n, true);
                  return true;
              }
          
              public static void main(String[] args) {
                  Scanner scan = new Scanner(System.in);
                  while(scan.hasNext()) {
                      int n = scan.nextInt();
                      primeBest(n);
                      System.out.println();
                  }
                  scan.close();
              }
          }
          

          当输入质数 2147483647 时,它会产生以下输出:

          执行了 23170 次检查,确定 2147483647 是 PRIME。

          【讨论】:

            【解决方案6】:

            当然有数百种素性检验,根据数的大小、特殊形式、因子大小等,都有各种优缺点。

            然而,在 java 中我发现最有用的是这个:

            BigInteger.valueOf(long/int num).isProbablePrime(int certainty);
            

            它已经实现,而且速度非常快(我发现填充 0–2^64 和确定性为 15 的 1000x1000 矩阵大约需要 6 秒)并且可能比我们凡人想出的任何东西都更好地优化。

            它使用了Baillie–PSW primality test 的一个版本,它没有已知的反例。 (虽然它可能使用稍弱的测试版本,有时可能会出错。也许)

            【讨论】:

              【解决方案7】:

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

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

              【讨论】:

                【解决方案8】:

                看看AKS primality test(及其各种优化)。这是一个在多项式时间内运行的确定性素性检验。

                在Java中有一个算法的实现from the University of Tuebingen (Germany) here

                【讨论】:

                • 维基百科:“虽然该算法具有巨大的理论重要性,但它未在实践中使用。对于 64 位输入,Baillie– PSW 是确定性的,运行速度快了许多数量级。对于更大的输入,(也是无条件正确的)ECPP 和 APR 测试的性能远远优于 AKS。这就是在 O(n) 的定义中省略 乘法常数 的实际结果。
                • 即使链接的实现也说“因此,AkS 测试仅对计算复杂性理论感兴趣。测试 2^13-1 需要大约 30 分钟才能有效实现。” 30 分钟测试号码 8191。这是一些非常缓慢的测试。有更快的 AKS 版本,但它仍然不是这个问题的好答案。
                • 实现链接显然又死了,尽管archive.org中仍然存在:web.archive.org/web/20150717104434/http://…
                【解决方案9】:

                您迈出了消除所有 2 的倍数的第一步。

                但是,你为什么停在那里?您可以消除除 3 之外的所有 3 的倍数,除 5 之外的所有 5 的倍数,等等。

                当你按照这个推理得出结论时,你会得到Sieve of Eratosthenes

                【讨论】:

                • 3 和 5 的倍数,将在 for 循环的前两次迭代中消除。埃拉托色尼筛法特别适用于生成一系列素数(恕我直言)
                • 你指的不是幂,而是倍数。
                【解决方案10】:

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

                最后,虽然编译器可能会为你优化这个,你应该写:

                int max =  (int) (Math.sqrt(n) + 1);
                for (int i = 3; i <= max; i = i + 2) {
                }
                

                【讨论】:

                  【解决方案11】:

                  这是最优雅的方式:

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

                  Java 1.4+。无需导入。

                  这么短。太美了。

                  【讨论】:

                  • 这个正则表达式基本上是对一元数字进行试除。在 Perl 中已经多次提到它;您可以在许多网站上看到它的解释,例如stackoverflow.com/questions/3329766 noulakaz.net/weblog/2007/03/18/… Java 中唯一的区别是 1) .matches() 匹配整个字符串,所以你不需要 ^$,以及 2) 而不是重复 1s(这很难在 Java 中),我创建一个全为空字符的字符串(通过使用新的 char 数组创建一个字符串),然后将它们与 . 匹配
                  • 如果“优雅”的意思是“聪明而简洁”,那么当然可以。如果“优雅”意味着“可读”,我会说不是。我当然不想在代码中遇到这种情况。
                  • @anula 比最简单的算法慢几万倍
                  • 这没有什么优雅的。
                  • 正则表达式本质上相当于除以正整数系列,这是worst case naive 解决数字是否为素数的方法。
                  【解决方案12】:

                  这是另一种方式:

                  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;
                  }
                  

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

                  编辑

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

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

                  所以我进行了几个测试。我创建了一个大小为Integer.MAX_VALUE/2BitSet 代表所有奇数,并使用素数筛找到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);
                      }
                  }
                  

                  我是这样跑的:

                  java -Xmx1024m -cp . Main 15
                  

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

                  【讨论】:

                  • isPrime3 预期失败: 但原为:
                  • (long)Math.sqrt(n) 应该是 (long)Math.sqrt(n)+1
                  • isPrime3 2213 毫秒 isPrime2 3039 毫秒 isPrime1 6030 毫秒你打败了我
                  • 你有关于 BigInteger 的说法的来源或证据吗?你使用什么确定性?我已经看到 isProbablePrime(1) 以数字 9 失败,所以在你的答案中暗示它 /always/ 有效显然是错误的,但是你有什么把握相信 int /is prime/?长呢?
                  • 由于这是 java isprime 搜索的第一个结果,我认为在这个答案中突出一个缺陷很重要。对于每一种确定性,人们都可能得到错误的答案。这是因为 isProbablePrime 使用 Random 实例来选择见证人(并且基于数字的长度,甚至覆盖确定性)。示例:ideone.com/t3lo9G
                  【解决方案13】:

                  根据您需要测试的数字的长度,您可以预先计算小值 (n Sieve of Eratosthenes 是生成此类预先计算列表的首选方法。

                  如果你的数字大于这个,你可以使用拉宾的素数检验。 Rabin primality test

                  【讨论】:

                    【解决方案14】:

                    如果您只是想找出一个数字是否是素数,这已经足够了,但如果您想找出从 0 到 n 的所有素数,更好的选择是Sieve of Eratosthenes

                    但这将取决于 java 对数组大小等的限制。

                    【讨论】:

                      【解决方案15】:

                      您所写的是大多数普通程序员所做的,并且在大多数情况下应该足够了。

                      但是,如果您追求“最佳科学算法”,则有许多变体(具有不同程度的确定性)记录在 http://en.wikipedia.org/wiki/Prime_number

                      例如,如果您有 70 位数字,则 JVM 的物理限制会阻止您的代码运行,在这种情况下您可以使用“Sieves”等。

                      再次,就像我说的,如果这是一个编程问题或软件中的一般使用问题,你的代码应该是完美的:)

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 2012-11-18
                        • 2012-05-22
                        • 2015-11-05
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        相关资源
                        最近更新 更多