找到下一个素数的最快代码


17

问题如下。

输入:整数n

输出:大于的最小素数n

挑战在于提供最快的代码来做到这一点。我将测试上的大小的初始值的代码 大致10^8 10^200直到它超过的规模扩大一倍1分钟我的电脑上10秒钟。

获胜的代码将找到输入量最大的下一个素数。

通过比较,用python编写的简单筛子能够找到比10^8大约20几秒钟大的下一个素数。

我可以在4GB RAM ubuntu计算机上对其进行测试的要求很严格。所有代码都必须是免费的(两种意义上),并且如果使用库,它们也必须是免费的并且易于安装。报告的任何虚假素数都会立即取消提交的资格。

如果代码是完全用该语言编写的,而无需使用外部库,那么我还将对每种编程语言的获奖者分别给予表彰。随着比赛的进行,我还将保持最快时间的运转表,以便人们可以看到他们的表现。

到目前为止的表

  • 蟒蛇。使用由提供的代码,一个惊人的357数字质数343239883006530485749095039954069660863471765007165270469723172959277159169882802606127982033072727748864815569574042901856099399985832190628701414555752857600000000000000000000000000000000000000002872284792758930912601189043411951050852357613658978971208596097634095500808832510259693761982135208603287199546795000697807728609476163156438356035166156820611是10秒内的最终数字primo。有人会打败这个第一名吗?


@PeterTaylor我认为这个问题与时间复杂度有关。这大约是几秒钟的实用速度。我认为这两件事可能大不相同。
felipa

当然,如果您坚持使用小型测试用例。但是,由于没有人愿意为另一个问题实施AKS,因此您将获得相同的答案。
彼得·泰勒

3
@PeterTaylor让我不同意。最终,网站流量的90%应该来自搜索引擎。谷歌搜索快速半素数分解多项式二次筛返回了原来的问题,我分别从#2和#4处获取了代码。我想在某个时候,这个问题的排名也会很高fast next prime function
primo

1
我认为OP无法更新他对答案的测试...
mbomb007 '16

Answers:


21

Python〜451位数字

这是我为半素数分解问题编写的库的一部分,其中删除了不必要的功能。它使用了Baillie-PSW素数测试,从技术上讲这是一个概率测试,但是到目前为止,还没有已知的伪素数-如果您能够找到一个(或提供证明不存在的证据)甚至可以获得现金奖励。 。

编辑:我还没有意识到Python具有内置的模块化指数。将我自己替换为内置组件可以使性能提高约33%。

my_math.py

# legendre symbol (a|m)
# note: returns m-1 if a is a non-residue, instead of -1
def legendre(a, m):
  return pow(a, (m-1) >> 1, m)

# strong probable prime
def is_sprp(n, b=2):
  d = n-1
  s = 0
  while d&1 == 0:
    s += 1
    d >>= 1

  x = pow(b, d, n)
  if x == 1 or x == n-1:
    return True

  for r in range(1, s):
    x = (x * x)%n
    if x == 1:
      return False
    elif x == n-1:
      return True

  return False

# lucas probable prime
# assumes D = 1 (mod 4), (D|n) = -1
def is_lucas_prp(n, D):
  P = 1
  Q = (1-D) >> 2

  # n+1 = 2**r*s where s is odd
  s = n+1
  r = 0
  while s&1 == 0:
    r += 1
    s >>= 1

  # calculate the bit reversal of (odd) s
  # e.g. 19 (10011) <=> 25 (11001)
  t = 0
  while s > 0:
    if s&1:
      t += 1
      s -= 1
    else:
      t <<= 1
      s >>= 1

  # use the same bit reversal process to calculate the sth Lucas number
  # keep track of q = Q**n as we go
  U = 0
  V = 2
  q = 1
  # mod_inv(2, n)
  inv_2 = (n+1) >> 1
  while t > 0:
    if t&1 == 1:
      # U, V of n+1
      U, V = ((U + V) * inv_2)%n, ((D*U + V) * inv_2)%n
      q = (q * Q)%n
      t -= 1
    else:
      # U, V of n*2
      U, V = (U * V)%n, (V * V - 2 * q)%n
      q = (q * q)%n
      t >>= 1

  # double s until we have the 2**r*sth Lucas number
  while r > 0:
      U, V = (U * V)%n, (V * V - 2 * q)%n
      q = (q * q)%n
      r -= 1

  # primality check
  # if n is prime, n divides the n+1st Lucas number, given the assumptions
  return U == 0

# primes less than 212
small_primes = set([
    2,  3,  5,  7, 11, 13, 17, 19, 23, 29,
   31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
   73, 79, 83, 89, 97,101,103,107,109,113,
  127,131,137,139,149,151,157,163,167,173,
  179,181,191,193,197,199,211])

# pre-calced sieve of eratosthenes for n = 2, 3, 5, 7
indices = [
    1, 11, 13, 17, 19, 23, 29, 31, 37, 41,
   43, 47, 53, 59, 61, 67, 71, 73, 79, 83,
   89, 97,101,103,107,109,113,121,127,131,
  137,139,143,149,151,157,163,167,169,173,
  179,181,187,191,193,197,199,209]

# distances between sieve values
offsets = [
  10, 2, 4, 2, 4, 6, 2, 6, 4, 2, 4, 6,
   6, 2, 6, 4, 2, 6, 4, 6, 8, 4, 2, 4,
   2, 4, 8, 6, 4, 6, 2, 4, 6, 2, 6, 6,
   4, 2, 4, 6, 2, 6, 4, 2, 4, 2,10, 2]

max_int = 2147483647

# an 'almost certain' primality check
def is_prime(n):
  if n < 212:
    return n in small_primes

  for p in small_primes:
    if n%p == 0:
      return False

  # if n is a 32-bit integer, perform full trial division
  if n <= max_int:
    i = 211
    while i*i < n:
      for o in offsets:
        i += o
        if n%i == 0:
          return False
    return True

  # Baillie-PSW
  # this is technically a probabalistic test, but there are no known pseudoprimes
  if not is_sprp(n): return False
  a = 5
  s = 2
  while legendre(a, n) != n-1:
    s = -s
    a = s-a
  return is_lucas_prp(n, a)

# next prime strictly larger than n
def next_prime(n):
  if n < 2:
    return 2
  # first odd larger than n
  n = (n + 1) | 1
  if n < 212:
    while True:
      if n in small_primes:
        return n
      n += 2

  # find our position in the sieve rotation via binary search
  x = int(n%210)
  s = 0
  e = 47
  m = 24
  while m != e:
    if indices[m] < x:
      s = m
      m = (s + e + 1) >> 1
    else:
      e = m
      m = (s + e) >> 1

  i = int(n + (indices[m] - x))
  # adjust offsets
  offs = offsets[m:]+offsets[:m]
  while True:
    for o in offs:
      if is_prime(i):
        return i
      i += o

示例测试脚本:

from time import clock
from my_math import *

n = i = 317**79
while True:
  i *= 317
  time1 = clock()
  n, o = next_prime(i), n
  span = clock()-time1
  if span > 10:
    break
  print(len(str(n)), span)
print(o)

选择了317的因数,因为它大约是的平方根10000,每次迭代大约添加2.5个数字(并且因为加倍太慢而无法通过)。输出显示当前数字位数和花费的时间。

样本结果:

201 0.13121248650317288
203 0.059535499623555505
206 0.9157767258129175
208 0.2583420518529589
211 0.15367400046653978
213 0.32343915218274955
216 1.3962866788935466
218 0.5986165839513125
221 0.973842206202185
223 2.346910291671148
...
428 0.932809896229827
431 4.345940056627313
433 9.511724255457068
436 6.089835998709333
438 1.3793498894412721
441 4.290633027381972
443 3.5102506044762833
446 3.1629148397352083
448 3.364759208223404
451 7.34668009481652
1551197868099891386459896063244381932060770425565921999885096817830297496627504652115239001983985153119775350914638552307445919773021758654815641382344720913548160379485681746575245251059529720935264144339378936233043585239478807971817857394193701584822359805681429741446927344534491412763713568490429195862973508863067230162660278070962484418979417980291904500349345162151774412157280412235743457342694749679453616265540134456421369622519723266737913

现在所有代码都与python 3兼容。


太快了!我将在几天内将其大小加倍(并进行确定性的素数测试)以使其正常运行,并将最大的数目放在表中。我怀疑您可能已经是赢家了。
felipa

1
FWIW,在Sage中,next_prime((2^520)*(10^200))在我的机器上大约需要15秒钟,因此乍一看脸红了,这是非常令人印象深刻的。但是... next_prime((2^520)*(10^200),proof=False)需要0.4秒,因为它仅检查伪素数。当位数超过64时,您的说法“没有已知的伪素数”正在消失,这令人信服。对于357位数字,我什至不敢相信缺乏反例。
2013年

@boothby值得注意的是,这与Maple使用的方法非常相同。该方法已于33年前发布,至今仍没有已知的伪素表示其准确性。
2013年

1
这就是为什么我使用Sage。实际上,“未知失败”实际上与“已知成功”是不同的。假设在400个数字以下有一个错误的伪素数。找到它需要数万亿年-但它仍然存在,挫败了任何证明“伪素=素数”的尝试。我将始终反对使用概率保证为零的“解决方案”。蒙特卡洛?当然可以 “这是最主要的,因为一个向导告诉我,可能是这样吗?” 不。
standby

1
@boothby您需要添加一个答案,以便我们在其下进行评论:)
felipa

6

具有GMP的C ++:567位

在GMP中使用Miller-Rabin实现。它可能返回假阳性,但是好运实际上以2 ^ -200的概率击中了一个。

#include <gmp.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>

double time() {
  struct timeval t;
  gettimeofday(&t, NULL);
  return t.tv_usec  * 1e-6 + t.tv_sec;
}

int main(int argc, char *argv[]) {
  mpz_t n, m;
  mpz_init_set_ui(n, 10);
  mpz_pow_ui(n, n, 200);
  mpz_init(m);
  for (int i = 0; true; i++, mpz_mul_ui(n, n, 2)) {
    double start = time();
    for (mpz_add_ui(m, n, 1); !mpz_millerrabin(m, 100); mpz_add_ui(m, m, 2)) ;
    double t = time() - start;
    gmp_printf("%d %Zd %f\n", i, m, t);
    if (t > 10.0) break;
  }
}

10^200 * 2^1216 + 361在慢速笔记本电脑上运行一段时间之前,先找到质数(567位)。


3

带有GMP模块的Perl,1300位数字

使用我的模块Math :: Prime :: Util及其GMP后端。确切的交叉点取决于您的计算机以及您是否具有最新的GMP库。所有代码都是免费的(这些模块位于github和CPAN上,并且GMP免费提供)。我已经在AWS的Ubuntu和台式机Ubuntu(以及Fedora,AIX和NetBSD等)上运行它们。

核心代码在C和C + GMP中。MPU的next_prime看到该数字太大,然后将其转发到GMP后端(如果未安装后端,则转发给纯Perl代码)。这将进行字符串化并将其转换为mpz,并将结果转换回输入对象类型或Math :: BigInt。next_prime本身可以:

  • 一个mod 30轮
  • 跟踪剩余的mod 23#,因此它可以对高达23的素数进行本征模运算
  • 对通过这些测试的事物可能进行的主要测试。

可能的主要测试是:

  • 使用mpz_gcd_ui检查微小的除数(在其中的64位中,最多检查101个)
  • 使用单计算的大基数检查小除数。根据输入大小,最多可填10k或40k。
  • 对于大于2 ^ 1600的值,请使用Treesieve执行进一步的试验划分。这可以更有效地完成。
  • 最终,完成了ES BPSW(以2为底的Miller-Rabin测试,然后再进行一个更强的Lucas测试)。

ES BPSW之前的所有操作都只是优化,当然我们需要next_prime。在Perl中,还可以使用Math :: BigInt模块(在具有可选Pari和GMP后端的内核中)实现next_prime。可以执行AES BPSW(如Pari),但没有进行优化。

我考虑过基于部分筛选的版本的优点,例如使用了2个优点。我不确定这是否会更好,因为在大多数情况下,由于间隙很小,我们会进行不必要的筛分,有时对于较大的间隙,我们将不得不重复多次。

该库实现了ECPP(包括证书),因此我们可以对结果进行证明,但是1200位数字对于包含多项式的最小默认集合确实太大了(有一种下载较大集合的方法-证明需要花费一些时间15分钟,比Pari的APR-CL快一点,但比WraithX的mpz_aprcl慢一点)。ECPP相对于APR-CL的一个缺点是,它具有更大的时间差异,因此很可能在平均时间到达之前,在某个数字上超过10秒。有证据表明,除非允许多线程软件,否则我们仅限于400位数范围内的内容。

#!/usr/bin/env perl
use warnings;
use strict;
use Math::Prime::Util ":all";
use Math::Prime::Util::GMP;  # Barf if the backend isn't installed
use Time::HiRes qw(gettimeofday tv_interval);
use Math::GMP;

my $n = Math::GMP->new(10) ** 200;
while (1) {
  my $start = [gettimeofday];
  my $np = next_prime($n);
  my $sec = tv_interval($start);
  my $len = length($n);
  die "next_prime $len = +",$np-$n," in $sec seconds\n" if $sec > 10;
  warn "  next_prime $len = +",$np-$n," in $sec seconds\n";
  $n *= 10;
}

我决定尝试使用primo使用的相同序列。它达到1191位,这是我们达到18138位的差距。我还使用最新的my_math.py测试了primo的代码。它的10 ^ e序列为630位,而他的序列为641位。无需大量预测试的紧凑型全Python代码给人留下了深刻的印象。


我仍然无法克服这个模块的速度。它不再困扰我对Perl作为数字处理工具的兴趣。我目前正在Math::GMP以一种不太会浪费mpz参考创建/销毁的方式进行重写。
2014年

真正的工作全在C + GMP中,因此它也可以全部在Python中工作。对于大型数字,Python比Perl 5具有一些重要的优势,我希望可以解决。顺便说一句,Math :: GMPz比Math :: GMP快,并且基本上公开了整个mpz API,尽管有时更脆弱并且调用起来有点怪异。在Math :: GMP中修复某些问题在我的待办事项清单上,而在其他事情之后。关于MPU,我曾考虑过将开发转换成两个C库,然后让Perl模块使用它。这将有助于使其在其他地方使用。
DanaJ

我进步很好。下面的循环运行超过10倍的速度,只是由于更好的参考文献管理:$x = new Math::GMP(0); $x += 3 for 1..1000000。完成后,我将发布到cpan上;您将是第一个知道的人之一))
primo 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.