Código mais rápido para encontrar o próximo primo

17

O problema é o seguinte.

Entrada: um número inteiron

Saída: O menor primo maior que n.

O desafio é fornecer o código mais rápido possível para isso. Testarei o código em valores começando no tamanho aproximado10^8 10^200 e dobrando de tamanho até que demore mais de um minuto e 10 segundos no meu computador.

O código vencedor encontrará o próximo primo para o maior tamanho de entrada.

A título de comparação, uma simples peneira escrita em python é capaz de encontrar o próximo primo maior do que 10^8em 20segundos.

O requisito de que eu possa testá-lo no meu computador ubuntu de 4 GB de RAM é rigoroso. Todo o código deve ser livre (nos dois sentidos) e, se usar bibliotecas, também deve ser gratuito e facilmente instalável. Quaisquer falsos números primos relatados desqualificarão imediatamente o envio.

Também atribuirei elogios separados aos vencedores em cada linguagem de programação se o código for totalmente escrito nessa linguagem sem o uso de bibliotecas externas. Também manterei a mesa dos tempos mais rápidos à medida que a competição avança, para que as pessoas possam ver como estão.

Tabela até agora

  • Pitão. Um 357número impressionante de dígitos 343239883006530485749095039954069660863471765007165270469723172959277159169882802606127982033072727748864815569574042901856099399985832190628701414555752857600000000000000000000000000000000000000002872284792758930912601189043411951050852357613658978971208596097634095500808832510259693761982135208603287199546795000697807728609476163156438356035166156820611era o número final em menos de 10 segundos, usando o código fornecido por primo. Alguém vai vencer esta primeira entrada?
felipa
fonte
11
Quase uma cópia exata do código-Challenge: A mais próxima Prime
Peter Taylor
@ PeterTaylor Essa pergunta é sobre a complexidade do tempo, eu acho. Trata-se de velocidade prática em segundos. Eu acho que essas duas coisas podem ser bem diferentes.
felipa
Claro, se você se ater a pequenos casos de teste. Mas como ninguém se preocupou em implementar o AKS para a outra pergunta, você obterá as mesmas respostas.
Peter Taylor
3
@ PeterTaylor me permite discordar. Eventualmente, 90% do tráfego de um site deve provir de mecanismos de pesquisa . Uma pesquisa no google por fatoração semiprime rápida e Peneira quadrática polinomial múltipla retornam o problema original de onde eu peguei meu código nos lugares 2 e 4, respectivamente. Imagino que, em algum momento, esse problema também tenha uma classificação bastante alta fast next prime function.
primo
11
Eu acho que o OP não foi capaz de atualizar seus testes das respostas ...
mbomb007

Respostas:

21

Python ~ 451 dígitos

Isso faz parte da biblioteca que escrevi para um problema de fatoração semiprime , com funções desnecessárias removidas. Ele usa o teste de primalidade Baillie-PSW , que é tecnicamente um teste probabilístico, mas, até o momento, não existem pseudo-tempos conhecidos - e há até uma recompensa em dinheiro se você puder encontrar um (ou por fornecer uma prova de que não existe). .

Edit : Eu não tinha percebido que o Python possui exponenciação modular embutida. Substituir o meu pelos resultados embutidos em um aumento de desempenho de cerca de 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

Um script de teste de amostra:

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)

Um fator de 317 foi escolhido, porque é aproximadamente a raiz quadrada de 10000, adicionando aproximadamente 2,5 dígitos por iteração (e porque a duplicação era muito lenta para ser realizada). A saída mostra o número atual de dígitos e o tempo gasto.

Resultados da amostra:

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 o código agora é compatível com python 3.

primo
fonte
Isso é surpreendentemente rápido! Vou executá-lo corretamente com o dobro de tamanho em alguns dias (e um teste determinístico de primalidade) e colocar o maior número na tabela. Eu suspeito que você já pode ser o vencedor.
felipa
11
FWIW, no Sage, next_prime((2^520)*(10^200))cerca de 15 segundos na minha máquina, então, à primeira vista, isso é bastante impressionante. No entanto ... next_prime((2^520)*(10^200),proof=False)leva 0,4 segundos porque verifica apenas a pseudoprimalidade. Sua afirmação de que "não existem pseudoprimes conhecidos" é muito convincente, pois o número de bits ultrapassa 64. Para 357 dígitos, não estou nem remotamente convencido pela falta de contra-exemplos.
Boothby
@ boothby, vale a pena notar que esse é o mesmo método usado pelo Maple . O fato de o método ter sido publicado há 33 anos e ainda não haver pseudo-tempo conhecido fala por seu grau de precisão.
Primo
11
É por isso que eu uso o Sage. "Não conhecido por falhar" não é realmente o mesmo que "conhecido por funcionar". Suponha que houvesse um pseudoprime falso com menos de 400 dígitos. Levaria trilhões de anos para encontrá-lo - mas ainda estaria lá, frustrando qualquer tentativa de provar 'pseudoprime = primo'. Eu sempre rebaixarei as "soluções" que usam métodos probabalísticos com garantia zero. Monte Carlo? Coisa certa. "É excelente porque um mago me disse que provavelmente era"? Não.
boothby
11
@boothby Você precisa adicionar uma resposta para que possamos comentar sob ela :)
felipa
6

C ++ com GMP: 567 dígitos

Usa a implementação de Miller-Rabin no GMP. Pode retornar um falso positivo, mas a boa sorte realmente atinge um com probabilidade 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;
  }
}

Encontra o primo 10^200 * 2^1216 + 361(567 dígitos) antes de executar o tempo no meu laptop lento.

Keith Randall
fonte
3

Perl com módulo GMP, 1300 dígitos

Usando meu módulo Math :: Prime :: Util e seu back-end GMP . O ponto de cruzamento exato dependerá do seu computador e se você possui a biblioteca GMP mais recente. Todo o código é gratuito (os módulos estão no github e no CPAN, e o GMP está disponível gratuitamente). Eu os executei no Ubuntu da AWS, bem como no Ubuntu de desktop (e Fedora, AIX, NetBSD etc.).

O código principal está em C e C + GMP. next_prime do MPU vê que o número é muito grande e o encaminha para o back-end GMP (ou código Perl puro, se o back-end não estiver instalado). Isso stringiifica e converte em um mpz e converte o resultado novamente no tipo de objeto de entrada ou Math :: BigInt. next_prime em si faz:

  • uma roda mod 30
  • mantém o controle do restante mod 23 # para que ele possa fazer módulos nativos para primos de até 23
  • provável teste principal sobre as coisas que passam por eles.

O provável teste principal é:

  • verifique se há minúsculos divisores usando mpz_gcd_ui (em dois de 64 bits, verifique até 101)
  • verifique se há pequenos divisores usando primoriais grandes calculados individualmente. Isso é primos de até 10k ou 40k, dependendo do tamanho da entrada.
  • para valores maiores que 2 ^ 1600, executa uma divisão de teste adicional usando uma peneira de árvore. Isso poderia ser feito com mais eficiência.
  • finalmente, o ES BPSW é feito (teste de Miller-Rabin com base 2 seguido de um teste de Lucas extra forte ).

Tudo antes do ES BPSW é apenas otimização, o que é claro que queremos para next_prime. O next_prime também é implementado no Perl usando o módulo Math :: BigInt (no núcleo com back-ends Pari e GMP opcionais). Isso faz o AES BPSW (como o Pari), mas não é tão otimizado.

Pensei nos méritos de uma versão baseada em peneira parcial, usando um intervalo de, por exemplo, 2 méritos. Só não tenho certeza se isso seria realmente melhor, pois na maioria das vezes faríamos peneiras desnecessárias, pois a lacuna era pequena e, às vezes, para uma lacuna grande, teríamos que repeti-la várias vezes.

A biblioteca implementa o ECPP (incluindo certificados) para que pudéssemos executar uma prova no resultado, mas 1200 dígitos são realmente grandes demais para o pequeno conjunto padrão de polinômios incluídos (existe um método para fazer o download de conjuntos maiores - as provas demorariam um pouco 15 minutos, um pouco mais rápido que o APR-CL da Pari, mas um pouco mais lento que o mpz_aprcl do WraithX). Uma desvantagem do ECPP vs. APR-CL é que ele tem mais variação de tempo, portanto há uma boa chance de exceder 10 segundos em algum número antes que o tempo médio chegue lá. Com uma prova, acho que estamos limitados a algo na faixa de 400 dígitos, a menos que permitamos software multiencadeado.

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

Eu decidi tentar com a mesma sequência usada pelo primo. Chegou a 1191 dígitos, já que atingimos uma lacuna de 18138. Também testei o código do primo usando o mais recente my_math.py. Ele chega a 630 dígitos com a sequência 10 ^ e 641 com a sequência. Muito impressionante para código compacto todo em Python sem muitos pré-testes.

DanaJ
fonte
Ainda não consigo entender o quão rápido é este módulo. Resparked meu interesse em perl como uma ferramenta de processamento de números. Atualmente, estou reescrevendo Math::GMPde uma maneira que não é tão inútil com a criação / destruição de referência do mpz.
Primo
O trabalho real é todo em C + GMP, portanto, também pode funcionar em Python. O Python tem algumas vantagens sérias sobre o Perl 5 para grandes números, que eu gostaria que pudessem ser resolvidos. A propósito, o Math :: GMPz é mais rápido que o Math :: GMP e tem basicamente toda a API do mpz exposta, embora seja mais quebradiça e um pouco estranha de chamar às vezes. Corrigir algumas coisas no Math :: GMP está na minha lista de tarefas por trás de muitas outras coisas. Re MPU, eu pensei em inverter o desenvolvimento e transformá-lo em duas bibliotecas C, então o módulo Perl apenas usa isso. Ajudaria a usá-lo em outro lugar.
DanaJ
Estou fazendo um bom progresso. Os seguintes corridas de loop mais de 10 vezes mais rápido , unicamente devido a uma melhor gestão de referência: $x = new Math::GMP(0); $x += 3 for 1..1000000. Vou postar no cpan quando terminar; você será um dos primeiros a saber;)
primo