Il codice più veloce per trovare il prossimo numero primo


17

Il problema è il seguente.

Input: un numero interon

Output: il primo più piccolo più grande di n.

La sfida è quella di fornire il codice più veloce possibile per farlo. Proverò il codice su valori che iniziano alla dimensione approssimativamente10^8 10^200 e raddoppiano la dimensione fino a quando non ci vogliono più di un minuto e 10 secondi sul mio computer.

Il codice vincente troverà il prossimo numero primo per la dimensione di input più grande.

A titolo di confronto, un semplice setaccio scritto in pitone è in grado di trovare il prossimo primo più grande che 10^8in circa 20secondi.

Il requisito che posso testarlo sul mio computer Ubuntu RAM da 4 GB è rigoroso. Tutto il codice deve essere libero (in entrambi i sensi) e se utilizza librerie deve anche essere libero e facilmente installabile. Eventuali numeri primi falsi segnalati squalificheranno immediatamente l'invio.

Premierò encomi separati per i vincitori anche in ciascun linguaggio di programmazione se il codice è interamente scritto in quella lingua senza l'uso di librerie esterne. Terrò anche una tabella dei tempi più veloci mentre la competizione procede in modo che le persone possano vedere come stanno andando.

Tavolo finora

  • Pitone. Un numero 357primo sorprendente è 343239883006530485749095039954069660863471765007165270469723172959277159169882802606127982033072727748864815569574042901856099399985832190628701414555752857600000000000000000000000000000000000000002872284792758930912601189043411951050852357613658978971208596097634095500808832510259693761982135208603287199546795000697807728609476163156438356035166156820611stato il numero finale inferiore a 10 secondi usando il codice fornito da primo. Qualcuno batterà questa prima voce?

1
Quasi un duplicato esatto di Code-Challenge: The Prime Prime
Peter Taylor,

@PeterTaylor Quella domanda riguarda la complessità temporale penso. Si tratta della velocità pratica in pochi secondi. Penso che queste due cose possano essere abbastanza diverse.
Felipa,

Certo, se ti attacchi a piccoli casi di test. Ma dal momento che nessuno si è preso la briga di implementare AKS per l'altra domanda, otterrai le stesse risposte.
Peter Taylor,

3
@PeterTaylor mi permette di non essere d'accordo. Alla fine, il 90% del traffico di un sito dovrebbe venire dai motori di ricerca . Una ricerca su google per una rapida fattorizzazione semiprime e il setaccio quadratico polinomiale multiplo restituiscono il problema originale che ho preso il mio codice rispettivamente al posto n. 2 e n. 4. Immagino che ad un certo punto, anche questo problema si classificherà abbastanza in alto fast next prime function.
primo

1
Penso che l'OP non abbia aggiornato i suoi test sulle risposte ...
mbomb007,

Risposte:


21

Python ~ 451 cifre

Questo fa parte della libreria che ho scritto per un problema di fattorizzazione semiprime , con le funzioni non necessarie rimosse. Utilizza il test di primalità Baillie-PSW , che è tecnicamente un test probabilistico, ma ad oggi, non ci sono pseudoprimi noti - e c'è anche una ricompensa in denaro se riesci a trovarne uno (o per fornire una prova che nessuno esiste) .

Modifica : non mi ero reso conto che Python avesse un'esponenziazione modulare integrata. Sostituendo il mio con i risultati integrati si ottiene un aumento delle prestazioni di circa il 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

Uno script di prova di esempio:

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)

È stato scelto un fattore di 317, perché è approssimativamente la radice quadrata di 10000, aggiungendo all'incirca 2,5 cifre per iterazione (e perché il raddoppio era troppo lento per passare). L'output mostra il numero corrente di cifre e il tempo impiegato.

Risultati del campione:

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

Tutto il codice è ora compatibile con Python 3.


È sorprendentemente veloce! Lo eseguirò correttamente con dimensioni raddoppiate in pochi giorni (e un test di primalità deterministica) e inserirò il numero più grande nella tabella. Sospetto che potresti essere già il vincitore, comunque.
Felipa,

1
FWIW, in Sage, next_prime((2^520)*(10^200))circa 15 secondi sulla mia macchina, quindi a prima vista questo è abbastanza impressionante. Tuttavia ... next_prime((2^520)*(10^200),proof=False)impiega 0,4 secondi perché verifica solo la pseudoprimalità. La tua affermazione "non ci sono pseudoprimi noti" è vanificante in quanto il numero di bit supera i 64. Per 357 cifre, non sono nemmeno lontanamente convinto dalla mancanza di controesempi.
stand

@boothby vale la pena notare che questo è lo stesso metodo utilizzato da Maple . Il fatto che il metodo sia stato pubblicato 33 anni fa e che non vi siano ancora pseudoprimi noti parla del suo grado di accuratezza.
primo

1
Questo è il motivo per cui uso Sage. "Not noto per fallire" in realtà non è lo stesso di "noto per funzionare". Supponiamo che ci fosse una falsa pseudoprima sotto le 400 cifre. Ci sarebbero voluti migliaia di miliardi di anni per trovarlo, ma sarebbe ancora lì, sviando qualsiasi tentativo di dimostrare "pseudoprime = prime". Sottovaluterò sempre le "soluzioni" che usano metodi probabalistici con garanzia zero. Monte Carlo? Cosa certa. "È fondamentale perché un mago mi ha detto che probabilmente lo era"? No.
stand,

1
@boothby Devi aggiungere una risposta in modo che possiamo commentare sotto di essa :)
felipa

6

C ++ con GMP: 567 cifre

Utilizza l'implementazione di Miller-Rabin in GMP. Potrebbe restituire un falso positivo, ma in realtà la fortuna ha colpito uno con probabilità 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;
  }
}

Trova le prime 10^200 * 2^1216 + 361(567 cifre) prima di correre nel tempo sul mio laptop lento.


3

Perl con modulo GMP, 1300 cifre

Usando il mio modulo Math :: Prime :: Util e il suo back-end GMP . Il punto esatto del crossover dipenderà dal tuo computer e dal fatto che tu abbia l'ultima libreria GMP. Tutto il codice è gratuito (i moduli sono su github e CPAN e GMP è disponibile gratuitamente). Li ho eseguiti su Ubuntu di AWS e su Ubuntu desktop (e Fedora, AIX e NetBSD, ecc.).

Il codice principale è in C e C + GMP. next_prime di MPU vede il numero troppo grande e lo inoltra al back-end GMP (o codice Perl puro se il back-end non è installato). Ciò stringiifica e converte in un mpz e convertirà nuovamente il risultato nel tipo di oggetto di input o Math :: BigInt. next_prime stesso fa:

  • una ruota mod 30
  • tiene traccia del restante mod 23 # in modo che possa eseguire moduli nativi per numeri primi fino a 23
  • probabile primo test su cose che li superano.

Il probabile test primo è:

  • controlla piccoli divisori usando mpz_gcd_ui (in 64 bit due di questi controllano fino a 101)
  • verifica la presenza di piccoli divisori usando primitive grandi calcolate singolarmente. Si tratta di numeri primi fino a 10k o 40k a seconda della dimensione di input.
  • per valori superiori a 2 ^ 1600, esegue un'ulteriore divisione di prova usando un setaccio. Questo potrebbe essere fatto in modo più efficiente.
  • infine, ES BPSW viene eseguito (test di Miller-Rabin con base 2 seguito da un test Lucas extra forte ).

Tutto ciò che precede l'ES BPSW è solo l'ottimizzazione, che ovviamente vogliamo per next_prime. next_prime è anche implementato in Perl usando il modulo Math :: BigInt (in core con back end Pari e GMP opzionali). Questo fa AES BPSW (come Pari) ma non è così ottimizzato.

Ho pensato ai meriti di una versione basata su setacci parziali, usando ad esempio un intervallo di 2 meriti. Non sono sicuro che sarebbe davvero meglio, dato che la maggior parte delle volte avremmo fatto un setaccio non necessario poiché il divario era piccolo, e talvolta per un grande divario dovremmo ripeterlo più volte.

La libreria implementa ECPP (compresi i certificati) in modo da poter eseguire una prova del risultato, ma 1200 cifre sono davvero troppo grandi per il piccolo set predefinito di polinomi inclusi (esiste un metodo per scaricare set più grandi - le prove richiederebbero un po 'di 15 minuti che è un po 'più veloce dell'APR-CL di Pari ma un po' più lento del mpz_aprcl di WraithX). Uno svantaggio di ECPP rispetto a APR-CL è che ha una maggiore variazione di tempo, quindi c'è una buona probabilità che superi i 10 secondi su un numero prima che arrivi il tempo medio. Con una prova penso che siamo limitati a qualcosa nella gamma di 400 cifre a meno che non consentiamo software multi-thread.

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

Ho deciso di provare con la stessa sequenza usata da primo. Sono arrivate a 1191 cifre, poiché è lì che abbiamo colmato una lacuna del 18138. Ho anche testato il codice di primo usando l'ultimo my_math.py. Arriva a 630 cifre con la sequenza 10 ^ e 641 con la sua sequenza. Molto impressionante per il codice all-Python compatto senza molte pretese.


Non riesco ancora a superare la velocità di questo modulo. Ha rispecchiato il mio interesse per il perl come strumento per scricchiolare i numeri. Attualmente sto riscrivendo Math::GMPin un modo non troppo dispendioso con la creazione / distruzione di riferimenti mpz.
primo

Il vero lavoro è tutto in C + GMP, quindi potrebbe funzionare anche in Python. Python ha alcuni seri vantaggi rispetto a Perl 5 per i grandi numeri, che vorrei poter essere risolto. Math :: GMPz, a proposito, è più veloce di Math :: GMP e ha sostanzialmente esposto l'intera API mpz, anche se a volte è più fragile e un po 'strano da chiamare. Risolvere alcune cose in Math :: GMP è nella mia lista delle cose da fare dietro troppe altre cose. Per quanto riguarda MPU, ho pensato di invertire lo sviluppo e trasformarlo in due librerie C, quindi basta che il modulo Perl lo usi. Aiuterebbe a usarlo altrove.
DanaJ,

Sto facendo buoni progressi. Viene eseguito il ciclo seguente oltre 10 volte più veloce , solo a causa di una migliore gestione di riferimento: $x = new Math::GMP(0); $x += 3 for 1..1000000. Pubblicherò su cpan quando avrò finito; sarai uno dei primi a saperlo;)
primo
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.