Найшвидший код, щоб знайти наступний прайм


17

Проблема полягає в наступному.

Введення: Ціле числоn

Вихід: Найменший праймер більший за n.

Завдання полягає в тому, щоб дати найшвидший код для цього. Я перевіряю код на значення, починаючи з розміру приблизно10^8 10^200 і подвоюючи розмір, поки на моєму комп’ютері не буде більше однієї хвилини 10 секунд.

Код, який виграє , знайде наступний простір для найбільшого розміру вводу.

Для порівняння, просте сито, написане пітоном, здатне знайти наступний простір більше, ніж 10^8приблизно за 20секунди.

Вимога, що я можу перевірити його на моєму комп'ютері 4 Гб оперативної пам'яті ubuntu, сувора. Весь код повинен бути вільним (в обох сенсах), і якщо він використовує бібліотеки, він також повинен бути вільним і легко встановлюватися. Повідомлення про помилкові праймери негайно дискваліфікують подання.

Я також присуджую окремі подяки переможцям у кожній мові програмування, якщо код повністю написаний цією мовою без використання зовнішніх бібліотек. Я також буду тримати стіл для бігу найшвидших часів, коли змагання триватимуть, щоб люди могли побачити, як у них справи.

Таблиця поки що

  • Пітон. Дивовижним 357розрядом числа 343239883006530485749095039954069660863471765007165270469723172959277159169882802606127982033072727748864815569574042901856099399985832190628701414555752857600000000000000000000000000000000000000002872284792758930912601189043411951050852357613658978971208596097634095500808832510259693761982135208603287199546795000697807728609476163156438356035166156820611було остаточне число за 10 секунд, використовуючи код, наданий primo. Хтось поб'є цей перший запис?


@PeterTaylor Я думаю, що питання стосується складності у часі. Йдеться про практичну швидкість за секунди. Я думаю, що ці дві речі можуть бути зовсім різними.
felipa

Звичайно, якщо ви дотримуєтесь невеликих тестових справ. Але оскільки ніхто не переймався впроваджувати AKS за іншим питанням, ви отримаєте однакові відповіді.
Пітер Тейлор

3
@PeterTaylor дозволить мені не погодитися. Врешті-решт, 90% трафіку сайту має надходити з пошукових систем . Пошук Google для швидкої факторизації напівпромінь та множинного поліноміального квадратичного сита повертає початкову проблему, яку я взяв зі свого коду з місця №2 та №4 відповідно. Я думаю, в якийсь момент ця проблема також буде досить високою fast next prime function.
примо

1
Я думаю, що ОП не змогла оновити свої тести відповідей ...
mbomb007

Відповіді:


21

Python ~ 451 цифр

Це частина бібліотеки, яку я написав у зв'язку із проблемою факторизації напівпрома , з видаленням зайвих функцій. Він використовує тест первинності Baillie-PSW , який технічно є ймовірнісним тестом, але на сьогоднішній день невідомі псевдозлочини - і навіть є грошова винагорода, якщо ви зможете її знайти (або за надання доказів, що жодного не існує) .

Редагувати : Я не розумів, що Python має вбудовану модульну експоненцію. Заміна моєї власної на вбудований призводить до підвищення продуктивності приблизно на 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 цифри за ітерацію (і тому, що подвоєння було занадто повільним, щоб простояти). Вихідні дані показують поточну кількість цифр та витрачений час.

Приклад результатів:

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
FWIW, у Sage, next_prime((2^520)*(10^200))близько 15 секунд на моїй машині, тож спочатку червоніє це досить вражаюче. Однак ... next_prime((2^520)*(10^200),proof=False)займає 0,4 секунди, тому що перевіряється лише на псевдосуб'єктність. Твоє твердження "немає відомих псевдозлочинностей" стає переконливо переконливим, оскільки кількість бітів перевищує 64. Що стосується 357 цифр, я навіть не віддалено переконаний у відсутності контрприкладів.
виставка

@boothby Варто зазначити, що це той самий метод, який використовує Maple . Про те, що метод був опублікований 33 роки тому, і досі невідомі псевдопригоди говорять про його ступінь точності.
примо

1
Ось чому я використовую шавлія. "Невідомо, що не вдалося" насправді насправді не те саме, що "відомо, що працює". Припустимо, був один помилковий псевдокрим під 400 цифр. Щоб його знайти, знадобиться трильйони років - але все одно він буде там, припиняючи будь-яку спробу довести "псевдоприклад = прем'єр". Я завжди виступаю за "рішення", які використовують імовірнісні методи з нульовою гарантією. Монте Карло? Ясна річ. "Це головне", тому що майстер сказав мені, що це, мабуть, було "? Ні.
кабінка

1
@boothby Вам потрібно додати відповідь, щоб ми могли прокоментувати її :)
felipa

6

C ++ з GMP: 567 цифр

Використовує реалізацію Міллера-Рабіна в GMP. Це може повернути хибний позитив, але удача насправді потрапила на один із вірогідністю 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

Perl з модулем GMP, 1300 цифр

Використовуючи мій модуль Math :: Prime :: Util та його GMP іззаду . Точна точка перетину залежить від вашого комп'ютера та від того, чи є у вас остання бібліотека GMP. Весь код безкоштовний (модулі розміщені на github та CPAN, а GMP є у вільному доступі). Я запускав їх на Ubuntu AWS, а також на Ubuntu на робочому столі (і Fedora, і AIX, і NetBSD тощо).

Основний код знаходиться в C і C + GMP. next_prime від MPU бачить номер занадто великим і пересилає його до заднього кінця GMP (або чистого коду Perl, якщо задній кінець не встановлений). Це строфіфікує та перетворює на mpz, і перетворює результат назад у тип вхідного об'єкта або Math :: BigInt. next_prime сам робить:

  • колесо мод 30
  • відслідковує залишок моди 23 #, щоб він міг робити власні модулі для праймерів до 23
  • ймовірний первинний тест на речі, які їх проходять.

Імовірний простий тест:

  • перевірити крихітні дільники за допомогою mpz_gcd_ui (у 64-бітових двох з них перевірити до 101)
  • перевірити на малі дільники, використовуючи одноразово обчислені великі передріччя. Це або праймери до 10 К, або 40 К, залежно від розміру вводу.
  • для значень більше 2 ^ 1600, здійснює подальше пробне ділення за допомогою деревного дерева. Це можна зробити ефективніше.
  • нарешті, робиться ES BPSW (тест Міллера-Рабіна з базою 2 з подальшим сильним тестом Лукаса ).

Все, що передує ES BPSW, - це лише оптимізація, якої, звичайно, ми хочемо для next_prime. next_prime також реалізований в Perl за допомогою модуля Math :: BigInt (в основному з додатковими кінцями Pari та GMP). Це робить AES BPSW (як Pari), але не настільки оптимізований.

Я подумав про переваги версії на основі часткового сита, використовуючи, наприклад, 2 достоїнства. Я просто не впевнений, чи справді це буде краще, оскільки більшість часу ми робимо непотрібне просіювання, оскільки розрив був невеликий, а іноді для великого проміжку нам доведеться повторити його кілька разів.

Бібліотека реалізує ECPP (включаючи сертифікати), щоб ми могли провести доказ результату, але 1200 цифр дійсно занадто великі для крихітного набору за замовчуванням включених многочленів (існує метод для завантаження більших наборів - докази знадобляться трохи нижче 15 хвилин, що трохи швидше, ніж APR-CL Пари, але трохи повільніше, ніж mpz_aprcl WraithX). Одним із недоліків 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. Він отримав 1191 цифру, тому саме там ми потрапили в розрив 18138. Я також протестував код примо, використовуючи останній my_math.py. Він отримує 630 цифр з послідовністю 10 ^ e і 641 з його послідовністю. Дуже вражаючий для компактного все-Python-коду без великої кількості причин.


Я досі не можу зрозуміти, наскільки швидко цей модуль. Це відзначило мій інтерес до perl як інструменту для стискання чисел. В даний час я переписую Math::GMPтак, що не так вже й марно, при створенні / знищенні посилань на mpz.
примо

Справжня робота вся в C + GMP, тому вона може працювати і в Python. Python має великі переваги перед Perl 5 для великих номерів, які я хотів би вирішити. Math :: GMPz, до речі, швидше, ніж Math :: GMP, і, в основному, весь mpz API піддається, хоч іноді більш крихким і трохи дивно називати. Виправлення деяких речей в Math :: GMP знаходиться в моєму списку todo за занадто багато інших речей. Щодо MPU, я думав про те, щоб перетворити розробку і перетворити її на дві бібліотеки С, тоді модуль Perl просто використовувати це. Це допоможе використати його в інших місцях.
DanaJ

Я добре просуваюся. Наступні запускає цикл більш 10 разів швидше , тільки за рахунок більш ефективного управління еталонним: $x = new Math::GMP(0); $x += 3 for 1..1000000. Я пошлю до cpan, коли закінчу; ви будете одними з перших, хто це дізнався;)
прим
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.