Mã nhanh nhất để tìm số nguyên tố tiếp theo


17

Vấn đề như sau.

Đầu vào: Một số nguyênn

Đầu ra: Số nguyên tố nhỏ nhất lớn hơn n.

Thách thức là đưa ra mã nhanh nhất có thể để làm điều này. Tôi sẽ kiểm tra mã trên các giá trị bắt đầu ở kích thước khoảng10^8 10^200 và tăng gấp đôi kích thước cho đến khi mất hơn một phút 10 giây trên máy tính của tôi.

Mã chiến thắng sẽ tìm thấy số nguyên tố tiếp theo cho kích thước đầu vào lớn nhất.

Bằng cách so sánh, một cái rây đơn giản viết bằng trăn có thể tìm thấy số nguyên tố tiếp theo lớn hơn 10^8trong khoảng 20vài giây.

Yêu cầu mà tôi có thể kiểm tra nó trên máy tính ub Ubuntu 4GB RAM của mình là rất nghiêm ngặt. Tất cả các mã phải miễn phí (theo cả hai nghĩa) và nếu nó sử dụng các thư viện thì chúng cũng phải miễn phí và dễ dàng cài đặt. Bất kỳ số nguyên tố sai được báo cáo sẽ ngay lập tức loại bỏ trình.

Tôi cũng sẽ trao phần thưởng riêng cho những người chiến thắng trong mỗi ngôn ngữ lập trình nếu mã được viết hoàn toàn bằng ngôn ngữ đó mà không sử dụng các thư viện bên ngoài. Tôi cũng sẽ giữ một bảng chạy thời gian nhanh nhất khi cuộc thi diễn ra để mọi người có thể thấy họ đang làm như thế nào.

Bảng cho đến nay

  • Con trăn. Một 357số nguyên tố đáng kinh ngạc 343239883006530485749095039954069660863471765007165270469723172959277159169882802606127982033072727748864815569574042901856099399985832190628701414555752857600000000000000000000000000000000000000002872284792758930912601189043411951050852357613658978971208596097634095500808832510259693761982135208603287199546795000697807728609476163156438356035166156820611là số cuối cùng dưới 10 giây sử dụng mã được cung cấp bởi primo. Bất cứ ai sẽ đánh bại mục đầu tiên này?

1
Gần như là một bản sao chính xác của Thử thách mã: Thủ tướng gần nhất
Peter Taylor

@PeterTaylor Câu hỏi đó là về sự phức tạp thời gian tôi nghĩ. Đây là về tốc độ thực tế trong vài giây. Tôi nghĩ rằng hai điều đó có thể khá khác nhau.
felipa

Chắc chắn, nếu bạn dính vào các trường hợp thử nghiệm nhỏ. Nhưng vì không ai bận tâm thực hiện AKS cho câu hỏi khác, nên bạn sẽ nhận được câu trả lời tương tự.
Peter Taylor

3
@PeterTaylor cho phép tôi không đồng ý. Cuối cùng, 90% lưu lượng truy cập của một trang web phải đến từ các công cụ tìm kiếm . Một tìm kiếm của google về hệ số hóa bán chính xác nhanhRây đa phương bậc hai trả về vấn đề ban đầu tôi đã lấy mã của mình từ vị trí số 2 và số 4 tương ứng. Tôi tưởng tượng đến một lúc nào đó, vấn đề này cũng sẽ được xếp hạng khá cao fast next prime function.
Primo

1
Tôi nghĩ rằng OP đã thất bại trong việc cập nhật các bài kiểm tra của mình về câu trả lời ...
mbomb007

Câu trả lời:


21

Python ~ 451 chữ số

Đây là một phần của thư viện tôi đã viết cho một vấn đề nhân tố bán kết , với các chức năng không cần thiết bị loại bỏ. Nó sử dụng thử nghiệm nguyên thủy Baillie-PSW , về mặt kỹ thuật là thử nghiệm xác suất, nhưng cho đến nay, không có giả danh nào được biết đến - và thậm chí còn có một phần thưởng tiền mặt nếu bạn có thể tìm thấy một (hoặc để cung cấp bằng chứng không tồn tại) .

Chỉnh sửa : Tôi đã không nhận ra rằng Python có lũy thừa mô đun tích hợp. Thay thế của riêng tôi cho các kết quả tích hợp trong tăng hiệu suất khoảng 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

Một kịch bản thử nghiệm mẫu:

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)

Một yếu tố của 317 đã được chọn, bởi vì nó xấp xỉ căn bậc hai 10000, thêm khoảng 2,5 chữ số cho mỗi lần lặp (và vì nhân đôi quá chậm để có thể ngồi qua). Đầu ra cho thấy số chữ số hiện tại và thời gian thực hiện.

Kết quả mẫu:

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

Tất cả các mã bây giờ là python 3 tương thích.


Đó là nhanh đáng kinh ngạc! Tôi sẽ chạy nó đúng cách với kích thước gấp đôi trong một vài ngày (và một bài kiểm tra tính nguyên thủy xác định) và đặt số lượng lớn nhất trong bảng. Tôi nghi ngờ bạn có thể đã là người chiến thắng mặc dù.
felipa

1
FWIW, trong Sage, next_prime((2^520)*(10^200))khoảng 15 giây trên máy của tôi, vì vậy, lúc đầu, điều này khá ấn tượng. Tuy nhiên ... next_prime((2^520)*(10^200),proof=False)mất 0,4 giây vì nó chỉ kiểm tra giả hành. Yêu cầu của bạn "không có giả danh nào được biết đến" đang thuyết phục một cách thuyết phục khi số bit vượt quá 64. Đối với 357 chữ số, tôi thậm chí còn không bị thuyết phục bởi việc thiếu các mẫu phản.
gian hàng

@boothby đáng lưu ý rằng đây là phương pháp tương tự được sử dụng bởi Maple . Phương pháp này đã được xuất bản cách đây 33 năm và vẫn chưa có giả danh nào nói lên mức độ chính xác của nó.
primo

1
Đây là lý do tại sao tôi sử dụng Sage. "Không biết là thất bại" thực sự không giống như "được biết là có tác dụng". Giả sử có một giả giả dưới 400 chữ số. Sẽ mất hàng nghìn tỷ năm để tìm thấy nó - nhưng nó vẫn ở đó, cho phép mọi nỗ lực chứng minh 'pseudoprime = Prime'. Tôi sẽ luôn downvote "giải pháp" sử dụng các phương pháp probabalistic với bảo đảm bằng không. Monte Carlo? Điều chắc chắn. "Đó là nguyên nhân" bởi vì một phù thủy nói với tôi rằng nó có thể là "? Không.
gian hàng

1
@boothby Bạn cần thêm câu trả lời để chúng tôi có thể nhận xét theo nó :)
felipa

6

C ++ với GMP: 567 chữ số

Sử dụng triển khai Miller-Rabin trong GMP. Nó có thể trả về kết quả dương tính giả, nhưng may mắn thực sự đạt được với xác suất 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;
  }
}

Tìm số nguyên tố 10^200 * 2^1216 + 361(567 chữ số) trước khi chạy theo thời gian trên máy tính xách tay chậm của tôi.


3

Perl với mô-đun GMP, 1300 chữ số

Sử dụng mô-đun Math :: Prime :: Util và phần cuối của nó . Điểm giao nhau chính xác sẽ phụ thuộc vào máy tính của bạn và liệu bạn có thư viện GMP mới nhất hay không. Tất cả các mã đều miễn phí (các mô-đun có trên github và CPAN, và GMP có sẵn miễn phí). Tôi đã chạy chúng trên Ubuntu của AWS cũng như Ubuntu trên máy tính để bàn (và Fedora, và AIX và NetBSD, v.v.).

Mã cốt lõi là trong C và C + GMP. next_prime từ MPU thấy số lượng quá lớn và chuyển tiếp nó đến mặt sau của GMP (hoặc mã Perl thuần nếu đầu cuối không được cài đặt). Điều đó xâu chuỗi và chuyển đổi thành mpz và sẽ chuyển đổi kết quả trở lại thành loại đối tượng đầu vào hoặc Math :: BigInt. next_prime chính nó:

  • một bánh xe mod 30
  • theo dõi mod còn lại 23 # để nó có thể thực hiện modul nguyên gốc cho các số nguyên tố lên tới 23
  • kiểm tra nguyên tố có thể xảy ra trên những thứ vượt qua những.

Bài kiểm tra chính có thể xảy ra là:

  • kiểm tra các ước số nhỏ bằng mpz_gcd_ui (trong 64-bit hai trong số này kiểm tra tới 101)
  • kiểm tra các ước số nhỏ bằng cách sử dụng các số nguyên thủy lớn được tính toán đơn lẻ. Đây là số nguyên tố lên tới 10k hoặc 40k tùy thuộc vào kích thước đầu vào.
  • đối với các giá trị lớn hơn 2 ^ 1600, thực hiện phân chia thử nghiệm thêm bằng cách sử dụng cây xanh. Điều này có thể được thực hiện hiệu quả hơn.
  • cuối cùng, ES BPSW đã hoàn thành (thử nghiệm Miller-Rabin với cơ sở 2 sau đó là thử nghiệm Lucas cực mạnh ).

Mọi thứ trước ES BPSW chỉ là tối ưu hóa, tất nhiên chúng tôi muốn cho next_prime. next_prime cũng được triển khai trong Perl bằng mô-đun Math :: BigInt (trong lõi với các đầu cuối Pari và GMP tùy chọn). Đó là AES BPSW (như Pari) nhưng không được tối ưu hóa.

Tôi đã nghĩ về giá trị của phiên bản dựa trên một phần sàng, sử dụng một phạm vi, ví dụ, 2 giá trị. Tôi chỉ không chắc liệu điều này có thực sự tốt hơn không, vì hầu hết thời gian chúng tôi sẽ làm những việc không cần thiết vì khoảng cách là nhỏ, và đôi khi đối với một khoảng cách lớn, chúng tôi phải lặp lại nhiều lần.

Thư viện triển khai ECPP (bao gồm các chứng chỉ) để chúng tôi có thể chạy bằng chứng về kết quả, nhưng 1200 chữ số thực sự quá lớn đối với tập hợp đa thức mặc định nhỏ (có một phương pháp để tải xuống các tập lớn hơn - bằng chứng sẽ mất một chút 15 phút, nhanh hơn một chút so với APR-CL của Pari nhưng chậm hơn một chút so với mpz_aprcl của WraithX). Một nhược điểm của ECPP so với APR-CL là nó có nhiều phương sai thời gian hơn nên có khả năng nó vượt quá 10 giây trên một số trước khi thời gian trung bình đến đó. Với một bằng chứng tôi nghĩ rằng chúng tôi giới hạn ở một cái gì đó trong phạm vi 400 chữ số trừ khi chúng tôi cho phép phần mềm đa luồng.

#!/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;
}

Tôi quyết định thử với trình tự tương tự được sử dụng bởi primo. Nó có tới 1191 chữ số, vì đó là nơi chúng tôi đạt khoảng cách là 18138. Tôi cũng đã kiểm tra mã của primo bằng cách sử dụng my_math.py mới nhất. Nó có tới 630 chữ số với chuỗi 10 ^ e và 641 với chuỗi của mình. Rất ấn tượng đối với mã all-Python nhỏ gọn mà không có nhiều giả thuyết.


Tôi vẫn không thể vượt qua được mô-đun này nhanh như thế nào. Nó phản ánh sự quan tâm của tôi đối với perl như một công cụ xử lý số. Tôi hiện đang viết lại Math::GMPtheo cách không quá lãng phí với việc tạo / hủy tham chiếu mpz.
primo

Công việc thực sự là tất cả trong C + GMP, vì vậy nó cũng có thể hoạt động với Python. Python có một số lợi thế nghiêm trọng so với Perl 5 đối với số lượng lớn, mà tôi muốn có thể được giải quyết. Math :: GMPz, nhân tiện, nhanh hơn Math :: GMP và về cơ bản có toàn bộ API mpz được phơi bày, mặc dù đôi khi dễ vỡ hơn và hơi lạ khi gọi. Sửa một số thứ trong Math :: GMP nằm trong danh sách việc cần làm của tôi đằng sau quá nhiều thứ khác. Re MPU, tôi đã nghĩ về việc đảo ngược sự phát triển và biến nó thành hai thư viện C, sau đó có mô-đun Perl chỉ sử dụng điều đó. Nó sẽ giúp làm cho nó được sử dụng ở nơi khác.
DanaJ

Tôi đang tiến bộ tốt. Vòng lặp sau chạy nhanh hơn 10 lần , chỉ nhờ quản lý tham chiếu tốt hơn : $x = new Math::GMP(0); $x += 3 for 1..1000000. Tôi sẽ đăng lên cpan khi tôi hoàn thành; bạn sẽ là một trong những người đầu tiên biết;)
primo
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.