Учитывая простое число N, вычислить следующее простое число? - PullRequest
67 голосов
/ 18 декабря 2010

Сотрудник только что сказал мне, что коллекция C # Dictionary изменяет размеры на простые числа по непонятным причинам, связанным с хешированием. И мой непосредственный вопрос был: «Как он узнает, что такое следующее простое число? Они рассказывают о гигантской таблице или вычисляют на лету?

Итак, мой вопрос, учитывая N, которое является простым числом, каков наиболее эффективный способ вычисления следующего простого числа?

Ответы [ 9 ]

74 голосов
/ 17 апреля 2011

Около года назад я работал в этой области для libc ++ при реализации неупорядоченных (хэш) контейнеров для C ++ 11.Я думал, что поделюсь своим опытом здесь.Этот опыт поддерживает принятый Marcog ответ для разумного определения "грубой силы".

Это означает, что даже простая грубая сила будет достаточно быстрой вВ большинстве случаев, принимая O (ln (p) * sqrt (p)) в среднем.

Я разработал несколько реализаций size_t next_prime(size_t n), где спецификация для этой функции:

Возвращает: Наименьшее простое число, которое больше или равно n.

Каждая реализация next_prime сопровождается вспомогательной функцией is_prime.is_prime следует считать частной реализацией;не предназначен для непосредственного вызова клиентом.Каждая из этих реализаций была, конечно, проверена на корректность, но также проверена с помощью следующего теста производительности:

int main()
{
    typedef std::chrono::high_resolution_clock Clock;
    typedef std::chrono::duration<double, std::milli> ms;
    Clock::time_point t0 = Clock::now();

    std::size_t n = 100000000;
    std::size_t e = 100000;
    for (std::size_t i = 0; i < e; ++i)
        n = next_prime(n+1);

    Clock::time_point t1 = Clock::now();
    std::cout << e/ms(t1-t0).count() << " primes/millisecond\n";
    return n;
}

Я должен подчеркнуть, что это тест производительности, и он не отражает типичное использование, которое выглядело бы болеенапример:

// Overflow checking not shown for clarity purposes
n = next_prime(2*n + 1);

Все тесты производительности были скомпилированы с:

clang++ -stdlib=libc++ -O3 main.cpp

Реализация 1

Существует семь реализаций.Цель отображения первой реализации состоит в том, чтобы продемонстрировать, что если вы не прекратите тестировать простое число кандидатов x для факторов на sqrt(x), то вам не удастся даже реализовать реализацию, которая может быть классифицирована как грубая сила.Эта реализация ужасно медленная .

bool
is_prime(std::size_t x)
{
    if (x < 2)
        return false;
    for (std::size_t i = 2; i < x; ++i)
    {
        if (x % i == 0)
            return false;
    }
    return true;
}

std::size_t
next_prime(std::size_t x)
{
    for (; !is_prime(x); ++x)
        ;
    return x;
}

Только для этой реализации мне пришлось установить e на 100 вместо 100000, просто чтобы получить разумное время выполнения:

0.0015282 primes/millisecond

Реализация 2

Эта реализация является самой медленной из реализаций brute force , и единственным отличием от реализации 1 является то, что она прекращает тестирование на простоту, когдакоэффициент превосходит sqrt(x).

bool
is_prime(std::size_t x)
{
    if (x < 2)
        return false;
    for (std::size_t i = 2; true; ++i)
    {
        std::size_t q = x / i;
        if (q < i)
            return true;
        if (x % i == 0)
            return false;
    }
    return true;
}

std::size_t
next_prime(std::size_t x)
{
    for (; !is_prime(x); ++x)
        ;
    return x;
}

Обратите внимание, что sqrt(x) не рассчитывается напрямую, а определяется q < i.Это ускоряет процесс в тысячи раз:

5.98576 primes/millisecond

и подтверждает предсказание Marcog:

... это вполне в рамках большинства проблем, принимающих порядокмиллисекунда на самом современном оборудовании.

Реализация 3

Можно почти удвоить скорость (по крайней мере, на оборудовании, которое я использую), избегая использованияоператора %:

bool
is_prime(std::size_t x)
{
    if (x < 2)
        return false;
    for (std::size_t i = 2; true; ++i)
    {
        std::size_t q = x / i;
        if (q < i)
            return true;
        if (x == q * i)
            return false;
    }
    return true;
}

std::size_t
next_prime(std::size_t x)
{
    for (; !is_prime(x); ++x)
        ;
    return x;
}

11.0512 primes/millisecond

Реализация 4

До сих пор я даже не использовал общеизвестный факт, что 2 является единственным четным простым числом.Эта реализация включает в себя эти знания, почти вдвое увеличивая скорость:

bool
is_prime(std::size_t x)
{
    for (std::size_t i = 3; true; i += 2)
    {
        std::size_t q = x / i;
        if (q < i)
            return true;
        if (x == q * i)
            return false;
    }
    return true;
}

std::size_t
next_prime(std::size_t x)
{
    if (x <= 2)
        return 2;
    if (!(x & 1))
        ++x;
    for (; !is_prime(x); x += 2)
        ;
    return x;
}

21.9846 primes/millisecond

Реализация 4, вероятно, имеет в виду большинство людей, когда они думают, что "грубая сила".

Реализация 5

Используя следующую формулу, вы можете легко выбрать все числа, которые не делятся ни на 2, ни на 3:

6 * k + {1, 5}

, где k> = 1. Следующая реализация использует эту формулу,но реализовано с помощью симпатичного трюка xor:

bool
is_prime(std::size_t x)
{
    std::size_t o = 4;
    for (std::size_t i = 5; true; i += o)
    {
        std::size_t q = x / i;
        if (q < i)
            return true;
        if (x == q * i)
            return false;
        o ^= 6;
    }
    return true;
}

std::size_t
next_prime(std::size_t x)
{
    switch (x)
    {
    case 0:
    case 1:
    case 2:
        return 2;
    case 3:
        return 3;
    case 4:
    case 5:
        return 5;
    }
    std::size_t k = x / 6;
    std::size_t i = x - 6 * k;
    std::size_t o = i < 2 ? 1 : 5;
    x = 6 * k + o;
    for (i = (3 + o) / 2; !is_prime(x); x += i)
        i ^= 6;
    return x;
}

Это фактически означает, что алгоритм должен проверять только 1/3 целых чисел на простоту вместо 1/2 чисел, а тест производительности показывает ожидаемоепочти на 50%:

32.6167 primes/millisecond

Реализация 6

Эта реализация является логическим расширением реализации 5: она использует следующую формулу для вычисления всех чисел, которыене делятся на 2, 3 и 5:

30 * k + {1, 7, 11, 13, 17, 19, 23, 29}

Он также развертывает внутренний цикл в is_prime и создает список "маленьких простых чисел", которыеЭто полезно для работы с числами меньше 30.

static const std::size_t small_primes[] =
{
    2,
    3,
    5,
    7,
    11,
    13,
    17,
    19,
    23,
    29
};

static const std::size_t indices[] =
{
    1,
    7,
    11,
    13,
    17,
    19,
    23,
    29
};

bool
is_prime(std::size_t x)
{
    const size_t N = sizeof(small_primes) / sizeof(small_primes[0]);
    for (std::size_t i = 3; i < N; ++i)
    {
        const std::size_t p = small_primes[i];
        const std::size_t q = x / p;
        if (q < p)
            return true;
        if (x == q * p)
            return false;
    }
    for (std::size_t i = 31; true;)
    {
        std::size_t q = x / i;
        if (q < i)
            return true;
        if (x == q * i)
            return false;
        i += 6;

        q = x / i;
        if (q < i)
            return true;
        if (x == q * i)
            return false;
        i += 4;

        q = x / i;
        if (q < i)
            return true;
        if (x == q * i)
            return false;
        i += 2;

        q = x / i;
        if (q < i)
            return true;
        if (x == q * i)
            return false;
        i += 4;

        q = x / i;
        if (q < i)
            return true;
        if (x == q * i)
            return false;
        i += 2;

        q = x / i;
        if (q < i)
            return true;
        if (x == q * i)
            return false;
        i += 4;

        q = x / i;
        if (q < i)
            return true;
        if (x == q * i)
            return false;
        i += 6;

        q = x / i;
        if (q < i)
            return true;
        if (x == q * i)
            return false;
        i += 2;
    }
    return true;
}

std::size_t
next_prime(std::size_t n)
{
    const size_t L = 30;
    const size_t N = sizeof(small_primes) / sizeof(small_primes[0]);
    // If n is small enough, search in small_primes
    if (n <= small_primes[N-1])
        return *std::lower_bound(small_primes, small_primes + N, n);
    // Else n > largest small_primes
    // Start searching list of potential primes: L * k0 + indices[in]
    const size_t M = sizeof(indices) / sizeof(indices[0]);
    // Select first potential prime >= n
    //   Known a-priori n >= L
    size_t k0 = n / L;
    size_t in = std::lower_bound(indices, indices + M, n - k0 * L) - indices;
    n = L * k0 + indices[in];
    while (!is_prime(n))
    {
        if (++in == M)
        {
            ++k0;
            in = 0;
        }
        n = L * k0 + indices[in];
    }
    return n;
}

Возможно, это выходит за рамки "грубой силы" и хорошо для увеличения скорости еще на 27,5% до:

41.6026 primes/millisecond

Реализация 7

Практически сыграть в вышеуказанную игру еще на одну итерацию, разработавформула для чисел, не делимых на 2, 3, 5 и 7:

210 * k + {1, 11, ...},

Исходный код здесь не показан, но очень похож на реализацию 6. Это реализация, которую я решил использовать для неупорядоченных контейнеров. libc ++ , и этот исходный код является открытым исходным кодом (находится по ссылке).

Эта последняя итерация хороша для еще одного увеличения скорости на 14,6% до:

47.685 primes/millisecond

Использование этого алгоритма гарантирует, что клиенты хеш-таблиц libc ++ могут выбирать любое простое число, которое они решат, наиболее выгодно для их ситуации, и производительность для этого приложения вполне приемлемо.

43 голосов
/ 18 декабря 2010

На всякий случай, если кому-то интересно:

Используя отражатель, я определил, что .Net использует статический класс, который содержит жестко закодированный список из ~ 72 простых чисел в диапазоне до 7199369, который сканирует наименьшее простое числопо крайней мере, в два раза больше текущего размера, и для размеров, больших, чем это, он вычисляет следующее простое число путем пробного деления всех нечетных чисел до sqrt потенциального числа.Этот класс является неизменным и потокобезопасным (т.е. большие простые числа не сохраняются для будущего использования).

34 голосов
/ 18 декабря 2010

Разрыв между последовательными простыми числами , как известно, довольно мал, с первым разрывом более 100 для простого числа 370261. Это означает, что даже простая грубая сила будет достаточно быстрой в большинстве случаев , принимая O (ln (p) * sqrt (p)) в среднем.

Для p = 10000 это O (921) операций. Учитывая, что мы будем выполнять это один раз при каждой вставке O (ln (p)) (грубо говоря), это вполне соответствует ограничениям большинства задач, принимаемых на миллисекундах на большинстве современных аппаратных средств.

12 голосов
/ 18 декабря 2010

Всего несколько экспериментов с промежуточным расстоянием.


Это дополнение для визуализации других ответов.

Я получил простые числа от 100.000-ой (= 1 299 709) до 200 000-ой (= 2 750 159)

Некоторые данные:

Maximum interprime distance = 148

Mean interprime distance = 15  

График частоты между расстояниями:

alt text

Межстандартное расстояние против простого числа

alt text

Просто чтобы увидеть, что оно «случайное». Однако ...

12 голосов
/ 18 декабря 2010

Хорошая хитрость заключается в использовании частичного сита.Например, каково следующее простое число, следующее за числом N = 2534536543556?

Проверьте модуль N относительно списка маленьких простых чисел.Таким образом ...

mod(2534536543556,[3 5 7 11 13 17 19 23 29 31 37])
ans =
     2     1     3     6     4     1     3     4    22    16    25

Мы знаем, что следующее простое число после N должно быть нечетным числом, и мы можем сразу отбросить все нечетные кратные этого списка маленьких простых чисел.Эти модули позволяют нам отсеивать кратные числа этих простых чисел.Если бы мы использовали небольшие простые числа до 200, мы могли бы использовать эту схему для немедленного отбрасывания большинства потенциальных простых чисел, больших N, за исключением небольшого списка.

Более явно, если мы ищем простое числоза 2534536543556 он не может делиться на 2, поэтому нам нужно рассмотреть только нечетные числа, выходящие за пределы этого значения.Вышеприведенные модули показывают, что 2534536543556 соответствует 2 модулю 3, поэтому 2534536543556 + 1 соответствует 0 модулю 3, так же как и 2534536543556 + 7, 2534536543556 + 13 и т. Д. По сути, мы можем отсеивать многие числа без необходимостичтобы проверить их на простоту и без пробных делений.

Аналогично, тот факт, что

mod(2534536543556,7) = 3

говорит нам, что 2534536543556 + 4 конгруэнтно 0 мод 7. Конечно, это числодаже, поэтому мы можем игнорировать это.Но 2534536543556 + 11 - это нечетное число, которое делится на 7, как и 2534536543556 + 25 и т. Д. Опять же, мы можем исключить эти числа как явно составные (потому что они делятся на 7) и поэтому не простые.

Используя только небольшой список простых чисел до 37, мы можем исключить большинство чисел, которые непосредственно следуют за нашей начальной точкой 2534536543556, за исключением нескольких:

{2534536543573 , 2534536543579 , 2534536543597}

Из этих чисел они простые?

2534536543573 = 1430239 * 1772107
2534536543579 = 99833 * 25387763

Я приложил усилия для обеспечения простых разложений первых двух чисел в списке.Посмотрите, что они составные, но главные факторы велики.Конечно, это имеет смысл, поскольку мы уже убедились, что ни одно из оставшихся чисел не может иметь небольших простых факторов.Третий в нашем коротком списке (2534536543597) на самом деле является самым первым простым числом после N. Описанная мною схема просеивания будет приводить к получению чисел, которые либо просты, либо состоят из, как правило, больших простых факторов.Таким образом, нам нужно было применить явный тест на простоту только к нескольким числам, прежде чем найти следующее простое число.

Аналогичная схема быстро дает следующее простое число, превышающее N = 1000000000000000000000000000, как 1000000000000000000000000103.

5 голосов
/ 18 декабря 2010

Нет функции f (n) для вычисления следующего простого числа. Вместо этого число должно быть проверено на простоту.

Также очень полезно при поиске n-го простого числа уже знать все простые числа от 1-го до (n-1) -го, потому что это единственные числа, которые необходимо проверить как факторы. *

В результате этих причин я не удивлюсь, если будет заранее рассчитан набор больших простых чисел. Для меня не имеет смысла, если некоторые простые числа нужно пересчитывать повторно.

3 голосов
/ 18 декабря 2010

Для абсолютной новизны всегда есть такой подход:

#!/usr/bin/perl
for $p ( 2 .. 200  ) {
      next if (1x$p) =~ /^(11+)\1+$/;
      for ($n=1x(1+$p); $n =~ /^(11+)\1+$/; $n.=1) { }
      printf "next prime after %d is %d\n", $p, length($n);
}

который, конечно, производит

next prime after 2 is 3
next prime after 3 is 5
next prime after 5 is 7
next prime after 7 is 11
next prime after 11 is 13
next prime after 13 is 17
next prime after 17 is 19
next prime after 19 is 23
next prime after 23 is 29
next prime after 29 is 31
next prime after 31 is 37
next prime after 37 is 41
next prime after 41 is 43
next prime after 43 is 47
next prime after 47 is 53
next prime after 53 is 59
next prime after 59 is 61
next prime after 61 is 67
next prime after 67 is 71
next prime after 71 is 73
next prime after 73 is 79
next prime after 79 is 83
next prime after 83 is 89
next prime after 89 is 97
next prime after 97 is 101
next prime after 101 is 103
next prime after 103 is 107
next prime after 107 is 109
next prime after 109 is 113
next prime after 113 is 127
next prime after 127 is 131
next prime after 131 is 137
next prime after 137 is 139
next prime after 139 is 149
next prime after 149 is 151
next prime after 151 is 157
next prime after 157 is 163
next prime after 163 is 167
next prime after 167 is 173
next prime after 173 is 179
next prime after 179 is 181
next prime after 181 is 191
next prime after 191 is 193
next prime after 193 is 197
next prime after 197 is 199
next prime after 199 is 211

За исключением всех развлечений и игр, хорошо известно, что оптимальный размер хэш-таблицы строго доказуемо простое число вида 4N−1 Так что просто найти следующий штрих недостаточно. Вы должны сделать другую проверку тоже.

3 голосов
/ 18 декабря 2010

Как уже отмечали другие, способ найти следующее простое число с учетом текущего простого числа еще не найден. Поэтому большинство алгоритмов больше фокусируются на использовании быстрых средств проверки простоты , поскольку вам нужно проверить n / 2 чисел между вашим известным простым числом и следующим.

В зависимости от приложения вы также можете просто жестко кодировать справочную таблицу, как отмечает Пол Уилер .

0 голосов
/ 18 декабря 2010

Насколько я помню, он использует простое число рядом с двойным текущего размера. Он не вычисляет это простое число - там таблица с предварительно загруженными числами до некоторого большого значения (не совсем, что-то около 10 000 000). Когда это число достигнуто, он использует некоторый наивный алгоритм для получения следующего числа (например, curNum = curNum + 1) и проверяет его, используя несколько следующих методов: http://en.wikipedia.org/wiki/Prime_number#Verifying_primality

...