次の素数を見つけるための最速のコード


17

問題は次のとおりです。

入力:整数n

出力:より大きい最小の素数n

課題は、これを行うために可能な限り高速なコードを提供することです。おおよその10^8 サイズから始まり、コンピューターで1分 10秒10^200以上かかるまで、サイズを2倍にして値のコードをテストします 。

勝ったコードは、最大入力サイズの次の素数を見つけます。

比較として、Pythonで書かれた単純なふるいは10^8、約20数秒で次の素数を見つけることができます。

4GB RAM ubuntuコンピューターでテストできるという要件は厳しいものです。すべてのコードは(両方の意味で)無料でなければならず、ライブラリを使用する場合は、無料で簡単にインストールできる必要があります。報告された偽の素数は、すぐに提出を失格にします。

コードが外部ライブラリを使用せずにその言語で完全に記述されている場合、各プログラミング言語の受賞者に対しても個別の表彰を授与します。また、競争が進むにつれて最速タイムのランニングテーブルを維持し、人々が自分の様子を確認できるようにします。

これまでのテーブル

  • Python。驚異的な357数字素数343239883006530485749095039954069660863471765007165270469723172959277159169882802606127982033072727748864815569574042901856099399985832190628701414555752857600000000000000000000000000000000000000002872284792758930912601189043411951050852357613658978971208596097634095500808832510259693761982135208603287199546795000697807728609476163156438356035166156820611は、によって提供されたコードを使用した10秒未満の最終的な数字でしたprimo。誰もがこの最初のエントリーを破りますか?


@PeterTaylorその質問は、私が思う時間の複雑さについてです。これは、秒単位の実用的な速度です。これらの2つのことはまったく異なる可能性があると思います。
フェリパ

もちろん、小さなテストケースにこだわる場合。しかし、誰も他の質問にAKSを実装することに煩わされていないので、あなたは同じ答えを得るでしょう。
ピーターテイラー

3
@PeterTaylorは私に反対を許します。最終的には、サイトのトラフィックの90%が検索エンジンから来るはずです。高速半素因数分解多重多項式二次ふるいのグーグル検索は、それぞれ場所#2と#4からコードを取得した元の問題を返します。ある時点で、この問題もかなり高いランクになると思いfast next prime functionます。
primo

1
OPは回答のテストを更新できなかったと思います
...-mbomb007

回答:


21

Python〜451桁

これは、不要な関数が削除された、半素因数分解問題用に作成したライブラリの一部です。技術的には確率的テストであるBaillie-PSW primality testを使用しますが、これまでのところ、既知の疑似プライムはありません。 。

編集: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互換になりました。


それは驚くほど速いです!数日でサイズを2倍にして(そして決定論的な素数テストで)適切に実行し、テーブルに最大数を入れます。あなたはすでに勝者かもしれないと思う。
フェリパ

1
FWIW、セージでは、next_prime((2^520)*(10^200))私のマシンで約15秒なので、最初は赤面しますが、これは非常に印象的です。ただし、... next_prime((2^520)*(10^200),proof=False)は疑似原始性のみをチェックするため、0.4秒かかります。ビット数が64を超えると、「既知の擬似プライムはありません」というあなたの主張は、紛れもなく説得力があります。
ブースビー

@boothbyこれは、Mapleで使用されている方法とまったく同じであることに注意してください。この方法は33年前に公開されたものであり、その精度の高さを示す疑似プリムはまだ知られてません。
primo

1
これが私がセージを使う理由です。「失敗することはわかっていません」は、実際には「動作することがわかっている」と同じではありません。400桁未満の偽の擬似プライムが1つあったとします。それを見つけるには何兆年もかかりますが、それはまだ存在し、「疑似プライム=プライム」を証明しようとする試みを阻止します。私は常に、保証がゼロの確率論的手法を使用する「ソリューション」に投票します。モンテカルロ?確実なこと。「それはおそらく魔法使いが私に言ったからだ」いや。
ブースビー

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ビットの2つで最大101までの確認)
  • 単独で計算された大きな原始関数を使用して小さな除数を確認します。これは、入力サイズに応じて、最大10kまたは40kの素数です。
  • 2 ^ 1600より大きい値の場合、treesieveを使用してさらに試行分割を実行します。これはより効率的に行うことができます。
  • 最後に、ES BPSWが実行されます(ベース2を使用したミラーラビンテスト、さらに強力なルーカステスト)。

ES BPSWより前のすべては最適化であり、当然next_primeに必要です。next_primeは、Math :: BigIntモジュール(オプションのPariおよびGMPバックエンドを備えたコア)を使用してPerlでも実装されます。これは、AES BPSW(Pariと同様)を行いますが、最適化されていません。

たとえば、2つのメリットの範囲を使用して、部分的なふるいベースのバージョンのメリットを考えました。ギャップが小さかったため、ほとんどの場合、不必要なふるいを行っていたので、これが本当に良いかどうかはわかりません。大きなギャップの場合は、何度も繰り返す必要があります。

ライブラリは結果に対して証明を実行できるようにECPP(証明書を含む)を実装しますが、含まれる多項式の小さなデフォルトセットには1200桁が実際には大きすぎます(大きなセットをダウンロードする方法があります-証明には少し時間がかかります) 15分。PariのAPR-CLよりも少し高速ですが、WraithXのmpz_aprclよりも少し遅くなります。ECPPとAPR-CLの欠点の1つは、時間変動が大きいため、平均時間が到達する前に、ある数で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で使用したものと同じシーケンスを試してみることにしました。18138のギャップに到達したため、これは1191桁になりました。また、最新のmy_math.pyを使用してprimoのコードをテストしました。10 ^ eシーケンスでは630桁、彼のシーケンスでは641桁になります。多くの事前テストのないコンパクトな全Pythonコードには非常に印象的です。


このモジュールがどれほど速いかはまだわかりません。数値計算ツールとしてのperlへの関心が再び高まった。私は現在Math::GMP、mpz参照の作成/破棄でそれほど無駄ではない方法で書き直しています。
primo 14

実際の作業はすべてC + GMPで行われるため、すべてPythonでも機能します。Pythonには、Perl 5に比べて大きな数字のいくつかの深刻な利点があります。ちなみにMath :: GMPzはMath :: GMPよりも高速で、基本的にmpz API全体が公開されていますが、より脆弱で、時々呼び出すのが少し奇妙です。Math :: GMPのいくつかの問題を修正することは、他にも多くのことの背後にある私のtodoリストに載っています。MPUについては、開発を反転させて2つのCライブラリにすることを考え、Perlモジュールにそれを使用させるだけです。他の場所で使用するのに役立ちます。
DanaJ 14

私は順調に進歩しています。次のループは参照管理が改善されたためだけに、10倍以上速く実行さます$x = new Math::GMP(0); $x += 3 for 1..1000000。完了したら、cpanに投稿します。あなたは最初に知る人の一人になります;)
primo 14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.