다음 소수를 찾는 가장 빠른 코드


17

문제는 다음과 같습니다.

입력 : 정수n

출력 : 보다 작은 가장 작은 소수 n.

문제는 가능한 가장 빠른 코드를 제공하는 것입니다. 내 컴퓨터에서 1 분 이상 10 초가 걸릴 때까지 대략10^8 크기에서 시작하여 크기가 10^200두 배가되는 값에 대한 코드를 테스트 합니다.

우승 코드 는 가장 큰 입력 크기의 다음 소수를 찾습니다.

비교하자면, 파이썬으로 작성된 간단한 체 10^8는 약 20몇 초 보다 큰 다음 소수를 찾을 수 있습니다.

4GB RAM 우분투 컴퓨터에서 테스트 할 수있는 요구 사항은 엄격합니다. 모든 코드는 자유로 워야하며 (두 가지 의미에서) 라이브러리를 사용하는 경우 자유롭고 쉽게 설치할 수 있어야합니다. 보고 된 허위 프라임은 제출을 즉시 실격시킵니다.

코드가 외부 라이브러리를 사용하지 않고 해당 언어로 완전히 작성된 경우에도 각 프로그래밍 언어에서 수상자에게 별도의 칭찬을 수여합니다. 또한 경쟁이 진행되는 동안 사람들이 자신의 활동을 확인할 수 있도록 가장 빠른 시간표를 유지합니다.

지금까지 테이블

  • 파이썬. 놀라운 357숫자 소수 343239883006530485749095039954069660863471765007165270469723172959277159169882802606127982033072727748864815569574042901856099399985832190628701414555752857600000000000000000000000000000000000000002872284792758930912601189043411951050852357613658978971208596097634095500808832510259693761982135208603287199546795000697807728609476163156438356035166156820611는에서 제공 한 코드를 사용하여 10 초 미만의 최종 숫자 primo입니다. 이 첫 번째 항목을 이길 사람이 있습니까?


@PeterTaylor 그 질문은 제가 생각하는 시간 복잡성에 관한 것입니다. 이것은 실제 속도 (초)입니다. 그 두 가지가 상당히 다를 수 있다고 생각합니다.
felipa

물론 작은 테스트 사례를 고수한다면 말입니다. 그러나 아무도 다른 질문에 대해 AKS를 구현하려고 귀찮게하지 않았으므로 동일한 대답을 얻을 수 있습니다.
피터 테일러

3
@PeterTaylor에 동의하지 않을 수 있습니다. 결국 사이트 트래픽의 90 %가 검색 엔진에서 발생합니다 . 빠른 semiprime factorizationMultiple Polynomial Quadratic Sieve에 대한 Google 검색은 각각 2 번과 4 번에서 코드를 가져온 원래 문제를 반환합니다. 어느 시점 에서이 문제는 상당히 높은 순위에 올 것이라고 상상 fast next prime function합니다.
primo 2019

1
OP가 답변 테스트를 업데이트하지 못한 것 같습니다 ...
mbomb007

답변:


21

파이썬 ~ 451 자리

이것은 세미 프라임 인수 분해 문제에 대해 작성한 라이브러리의 일부이며 불필요한 기능이 제거되었습니다. 기술적으로 확률 론적 테스트 인 Baillie-PSW primality test를 사용 하지만 현재까지 알려진 의사 프라임은 없습니다. 발견 할 수 없으면 현금 보상도 있습니다 (또는 존재하지 않는 증거를 제공하는 경우). .

편집 : 파이썬에 내장 모듈 식 지수가 있다는 것을 몰랐습니다. 빌트인으로 교체하면 약 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 자리를 더하고 (더블링이 너무 느리기 때문에) 317이 선택되었습니다 . 출력은 현재 자릿수와 소요 시간을 보여줍니다.

샘플 결과 :

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
FAGE, Sage에서는 next_prime((2^520)*(10^200))내 컴퓨터 에서 약 15 초가 걸렸습니다. 처음에는 홍당무가 인상적입니다. 그러나 next_prime((2^520)*(10^200),proof=False)의사 유사성 만 검사하기 때문에 0.4 초가 걸립니다. 비트 수가 64 개가 넘으면 "의사 프라임이 없다"는 주장이 사라지고있다 .
boothby

@boothby 메이플에서 사용하는 것과 동일한 방법이라는 점은 주목할 가치가 있습니다. 이 방법은 33 년 전에 발표 되었지만 여전히 의사 유사성에 대해서는 알려진 정도가 없습니다.
primo 2019

1
이것이 제가 Sage를 사용하는 이유입니다. "실패한 것으로 알려지지 않음"은 실제로는 "작동하는 것으로 알려진"것과 동일하지 않습니다. 400 자리 미만의 거짓 의사 프라임이 하나 있다고 가정합니다. 그것을 찾는 데 수십억 년이 걸릴 것이지만 여전히 '의사 프라임 = 프라임'을 증명하려는 시도를 망쳐 놓고있을 것입니다. 나는 항상 제로 보장없이 확률 론적 방법을 사용하는 "솔루션"을 다운 보트 할 것이다. 몬테카를로? 확실한 것. "마법사가 나에게 그런 말을했기 때문에 가장 중요합니다"? 아니.
boothby

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 :: UtilGMP 백엔드 사용 . 정확한 크로스 오버 포인트는 컴퓨터와 최신 GMP 라이브러리가 있는지 여부에 따라 다릅니다. 모든 코드는 무료입니다 (모듈은 github 및 CPAN에 있으며 GMP는 무료로 제공됩니다). AWS Ubuntu와 데스크톱 Ubuntu (및 Fedora, AIX 및 NetBSD 등)에서 실행했습니다.

핵심 코드는 C 및 C + GMP입니다. MPU의 next_prime에서 숫자가 너무 커서 GMP 백엔드 (또는 백엔드가 설치되지 않은 경우 순수한 Perl 코드)로 전달합니다. 이를 통해 문자열을 mpz로 변환하고 결과를 입력 객체 유형 또는 Math :: BigInt로 다시 변환합니다. next_prime 자체는 다음을 수행합니다.

  • 모드 30 휠
  • 나머지 mod 23 #을 추적하여 최대 23까지 소수에 대해 네이티브 모듈로를 수행 할 수 있습니다.
  • 이것들을 통과하는 것에 대한 가능한 주요 테스트.

가능한 주요 테스트는 다음과 같습니다.

  • mpz_gcd_ui를 사용하여 작은 제수를 확인하십시오 (이 중 64 비트 2 개에서 최대 101까지).
  • 단일 계산 된 큰 원시를 사용하여 작은 제수를 확인하십시오. 이것은 입력 크기에 따라 최대 10k 또는 40k입니다.
  • 2 ^ 1600보다 큰 값의 경우 나무를 사용하여 추가 시험 분할을 수행합니다. 이것은보다 효율적으로 이루어질 수 있습니다.
  • 마지막으로 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의 한 가지 단점은 시간 차이가 더 많기 때문에 평균 시간에 도달하기 전에 일부 숫자에서 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 코드에 매우 인상적입니다.


이 모듈이 얼마나 빠른지 여전히 극복 할 수 없습니다. 펄에 대한 관심을 숫자로 바꾸는 도구로 바 꾸었습니다. 나는 현재 Math::GMPmpz 참조 작성 / 파괴로 그렇게 낭비되지 않는 방식으로 다시 작성 하고 있습니다.
primo

실제 작업은 모두 C + GMP에 있으므로 Python에서도 모두 작동 할 수 있습니다. 파이썬은 큰 숫자에 대해 Perl 5에 비해 몇 가지 심각한 이점이 있습니다. 그건 그렇고, Math :: GMPz는 Math :: GMP보다 빠르며 기본적으로 전체 mpz API가 노출되어 있지만 때로는 더 부서지기 쉽고 약간 이상합니다. Math :: GMP에서 일부를 수정하면 너무 많은 다른 것들이 내 할 일 목록에 있습니다. Re MPU, 개발을 뒤집어 두 개의 C 라이브러리로 만든 다음 Perl 모듈을 사용하도록 생각했습니다. 다른 곳에서 사용하는 데 도움이됩니다.
DanaJ

나는 좋은 진전을 보이고 있습니다. 다음 루프는 더 나은 참조 관리로 인해 10 배 이상 빠르게 실행됩니다 $x = new Math::GMP(0); $x += 3 for 1..1000000. 완료되면 cpan에 게시합니다. 당신은 가장 먼저 알게 될 것입니다;)
primo
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.