El código más rápido para encontrar el próximo número primo

17

El problema es el siguiente.

Entrada: un enteron

Salida: El primo más pequeño más grande que n.

El desafío es dar el código más rápido posible para hacer esto. Probaré el código en valores que comiencen con un tamaño aproximado10^8 10^200 y dupliquen el tamaño hasta que tome más de un minuto y 10 segundos en mi computadora.

El código ganador encontrará el próximo primo para el tamaño de entrada más grande.

A modo de comparación, un tamiz simple escrito en python es capaz de encontrar el próximo cebado más grande que 10^8en unos 20segundos.

El requisito de que pueda probarlo en mi computadora ubuntu de 4 GB de RAM es estricto. Todo el código debe ser libre (en ambos sentidos) y si usa bibliotecas, también debe ser libre y fácil de instalar. Cualquier primo falso reportado descalificará de inmediato el envío.

También otorgaré elogios por separado para los ganadores en cada lenguaje de programación si el código está completamente escrito en ese idioma sin el uso de bibliotecas externas. También mantendré una tabla de los tiempos más rápidos a medida que avanza la competencia para que las personas puedan ver cómo están.

Tabla hasta ahora

  • Pitón. Un 357número primo asombroso 343239883006530485749095039954069660863471765007165270469723172959277159169882802606127982033072727748864815569574042901856099399985832190628701414555752857600000000000000000000000000000000000000002872284792758930912601189043411951050852357613658978971208596097634095500808832510259693761982135208603287199546795000697807728609476163156438356035166156820611fue el número final en menos de 10 segundos usando el código provisto por primo. ¿Alguien superará esta primera entrada?
felipa
fuente
1
Casi un duplicado exacto de Code-Challenge: The Nearest Prime
Peter Taylor
@ PeterTaylor Esa pregunta es sobre la complejidad del tiempo, creo. Se trata de velocidad práctica en segundos. Creo que esas dos cosas pueden ser bastante diferentes.
felipa
Claro, si te apegas a pequeños casos de prueba. Pero dado que nadie se molestó en implementar AKS para la otra pregunta, obtendrá las mismas respuestas.
Peter Taylor
3
@PeterTaylor me permite estar en desacuerdo. Finalmente, el 90% del tráfico de un sitio debe provenir de los motores de búsqueda . Una búsqueda en Google para la factorización rápida de semiprime y el Tamiz cuadrático polinómico múltiple devuelve el problema original del que tomé mi código en el lugar # 2 y # 4 respectivamente. Me imagino que en algún momento, este problema también será bastante alto fast next prime function.
primo
1
Creo que el PO no ha podido actualizar sus pruebas de las respuestas ...
mbomb007

Respuestas:

21

Python ~ 451 dígitos

Esto es parte de la biblioteca que escribí para un problema de factorización de semiprime , con funciones innecesarias eliminadas. Utiliza la prueba de primalidad Baillie-PSW , que técnicamente es una prueba probabilística, pero hasta la fecha, no hay pseudoprimos conocidos, e incluso hay una recompensa en efectivo si puede encontrar una (o por proporcionar una prueba de que no existe) .

Editar : no me había dado cuenta de que Python tiene una exponenciación modular incorporada. Reemplazar el mío por los resultados incorporados en un aumento del rendimiento de aproximadamente el 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

Un script de prueba de muestra:

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)

Se eligió un factor de 317, porque es aproximadamente la raíz cuadrada de 10000, agregando aproximadamente 2.5 dígitos por iteración (y porque duplicar era demasiado lento para sentarse). La salida muestra el número actual de dígitos y el tiempo necesario.

Resultados de muestra:

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

Todo el código ahora es compatible con Python 3.

primo
fuente
¡Eso es asombrosamente rápido! Lo ejecutaré correctamente con el tamaño duplicado en unos días (y una prueba de primalidad determinista) y pondré el mayor número en la tabla. Sin embargo, sospecho que ya puedes ser el ganador.
felipa
1
FWIW, en Sage, next_prime((2^520)*(10^200))unos 15 segundos en mi máquina, así que a primera vista esto es bastante impresionante. Sin embargo ... next_prime((2^520)*(10^200),proof=False)toma 0.4 segundos porque solo verifica la pseudoprimidad. Su afirmación "no se conocen pseudoprimos" es muy convincente a medida que el número de bits supera los 64. Para 357 dígitos, ni siquiera estoy convencido de forma remota por la falta de contraejemplos.
stand
@boothby vale la pena señalar que este es el mismo método utilizado por Maple . Que el método se publicó hace 33 años y que todavía no se conocen pseudoprimos habla de su grado de precisión.
primo
1
Por eso uso Sage. "No se sabe que falla" en realidad no es lo mismo que "se sabe que funciona". Supongamos que hay un falso seudoprimo de menos de 400 dígitos. Tardaría billones de años en encontrarlo, pero aún estaría allí, frustrando cualquier intento de probar 'pseudoprime = prime'. Siempre rechazaré las "soluciones" que utilizan métodos probabalísticos con cero garantías. ¿Monte Carlo? Cosa segura. "¿Es primordial porque un mago me dijo que probablemente lo era"? No
stand
1
@boothby Debe agregar una respuesta para que podamos comentar debajo de ella :)
felipa
6

C ++ con GMP: 567 dígitos

Utiliza la implementación de Miller-Rabin en GMP. Puede devolver un falso positivo, pero la buena suerte en realidad golpea a uno con probabilidad 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;
  }
}

Encuentra el primo 10^200 * 2^1216 + 361(567 dígitos) antes de correr con el tiempo en mi laptop lenta.

Keith Randall
fuente
3

Perl con módulo GMP, 1300 dígitos

Usando mi módulo Math :: Prime :: Util y su back end GMP . El punto de cruce exacto dependerá de su computadora y de si tiene la última biblioteca GMP. Todo el código es gratuito (los módulos están en github y CPAN, y GMP está disponible gratuitamente). Los ejecuté en Ubuntu de AWS, así como en Ubuntu de escritorio (y Fedora, y AIX, y NetBSD, etc.).

El código central está en C y C + GMP. next_prime de MPU ve que el número es demasiado grande y lo reenvía al back-end de GMP (o al código puro de Perl si el back-end no está instalado). Eso stringiifica y convierte a un mpz, y convertirá el resultado nuevamente en el tipo de objeto de entrada o Math :: BigInt. next_prime en sí hace:

  • una rueda mod 30
  • realiza un seguimiento del resto del mod 23 # para que pueda hacer módulos nativos para primos de hasta 23
  • Probable prueba principal sobre las cosas que pasan estos.

La prueba principal probable es:

  • compruebe si hay divisores pequeños usando mpz_gcd_ui (en 64 bits, dos de estos comprueban hasta 101)
  • compruebe si hay divisores pequeños utilizando primarios grandes calculados individualmente. Esto es primos de hasta 10k o 40k dependiendo del tamaño de entrada.
  • para valores mayores que 2 ^ 1600, realiza una división de prueba adicional usando un treeieve. Esto podría hacerse de manera más eficiente.
  • finalmente, se realiza ES BPSW (prueba de Miller-Rabin con base 2 seguida de una prueba extra fuerte de Lucas ).

Todo lo anterior al ES BPSW es ​​solo optimización, que por supuesto queremos para next_prime. next_prime también se implementa en Perl usando el módulo Math :: BigInt (en el núcleo con los extremos opcionales Pari y GMP). Eso hace AES BPSW (como Pari) pero no está tan optimizado.

He pensado en los méritos de una versión basada en tamiz parcial, utilizando un rango de, por ejemplo, 2 méritos. Simplemente no estoy seguro de si esto sería realmente mejor, ya que la mayoría de las veces estaríamos haciendo un tamizado innecesario ya que el espacio es pequeño, y a veces para un espacio grande tendríamos que repetirlo varias veces.

La biblioteca implementa ECPP (incluidos los certificados) para que podamos ejecutar una prueba del resultado, pero 1200 dígitos es realmente demasiado grande para el pequeño conjunto predeterminado de polinomios incluidos (hay un método para descargar conjuntos más grandes; las pruebas tomarían un poco menos de tiempo). 15 minutos, que es un poco más rápido que el APR-CL de Pari pero un poco más lento que el mpz_aprcl de WraithX). Una desventaja de ECPP vs. APR-CL es que tiene más variación de tiempo, por lo que hay una buena posibilidad de que exceda los 10 segundos en algún número antes de que llegue el tiempo promedio. Con una prueba, creo que estamos limitados a algo en el rango de 400 dígitos a menos que permitamos software de subprocesos múltiples.

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

Decidí probar con la misma secuencia utilizada por primo. Llegó a los 1191 dígitos, ya que es allí donde llegamos a una brecha de 18138. También probé el código de primo usando el último my_math.py. Llega a 630 dígitos con la secuencia 10 ^ e y 641 con su secuencia. Muy impresionante para el código compacto de Python sin muchas pruebas preliminares.

DanaJ
fuente
Todavía no puedo superar lo rápido que es este módulo. Ha reavivado mi interés en Perl como una herramienta de cálculo numérico. Actualmente estoy reescribiendo Math::GMPde una manera que no es tan derrochadora con la creación / destrucción de referencias mpz.
primo
El verdadero trabajo es todo en C + GMP, por lo que también podría funcionar en Python. Python tiene algunas ventajas serias sobre Perl 5 para grandes números, que desearía poder resolver. Math :: GMPz, por cierto, es más rápido que Math :: GMP y tiene básicamente toda la API de mpz expuesta, aunque a veces es más frágil y un poco extraño. Arreglar algunas cosas en Math :: GMP está en mi lista de tareas pendientes detrás de muchas otras cosas. Re MPU, he pensado en invertir el desarrollo y convertirlo en dos bibliotecas C, luego hacer que el módulo Perl solo lo use. Ayudaría a usarlo en otro lugar.
DanaJ
Estoy haciendo un buen progreso. Los siguientes se ejecute el bucle más de 10 veces más rápido , debido únicamente a una mejor gestión de referencia: $x = new Math::GMP(0); $x += 3 for 1..1000000. Publicaré en cpan cuando termine; serás uno de los primeros en saber;)
primo