Тест миллера (теория чисел)

Время работы

В случае, когда верхняя граница перебора задаётся функцией f(n)=2⋅ln2(n){\displaystyle f(n)=2\cdot ln^{2}(n)}, алгоритм детерминировано проверяет простоту числа n за O(ln4(n)){\displaystyle O(ln^{4}(n))}

, но при этом алгоритм опирается на обобщенную гипотезу Римана. Проще говоря, трудоемкость алгоритма растет быстрее чем O(ln2(n)){\displaystyle O(ln^{2}(n))}, (количество проверяемых оснований на псевдопростоту), потому что чем больше основание, тем более трудоемкий его анализ. От размера (меры) входных данных: длины записи m{\displaystyle m}, проверяемого числа n{\displaystyle n} , трудоёмкость алгоритма значит, зависит так: O(m4){\displaystyle O(m^{4})}, то есть полиномиальная сложность, 4-й степени.

В случае, когда f(n)=n0.133{\displaystyle f(n)=n^{0.133}}, время работы в худшем случае оценивается как O(n17){\displaystyle O(n^{1/7})} .
От размера входных данных: длины записи m{\displaystyle m}, проверяемого числа n{\displaystyle n}, трудоёмкость алгоритма, зависит так: O(em7){\displaystyle O(e^{m/7})}, то есть экспоненциальная сложность степени 1/7. Этот алгоритм намного сложнее: все экспоненциальные алгоритмы, при достаточно большом размере входных данных, становятся существенно более трудоемкими, чем все полиномиальные алгоритмы.

Описание алгоритма

Принцип работы

Тест Миллера основывается на том, что нечётное составное число n{\displaystyle n} либо является степенью некоторого простого числа, либо существует простое число, лежащее в интервале от 2{\displaystyle 2} до некоторой функции f(n){\displaystyle f(n)}, зависящей от n{\displaystyle n}, не являющееся данного числа по Миллеру.

Алгоритм

Ввод: n > 2, нечётное натуральное число, которое необходимо проверить на простоту;
Вывод: составное, означает, что n является составным числом;
       простое, означает, что n является простым числом.
(1)  Проверить, является ли n степенью какого-либо числа.
     если является, то вернуть составное
(2)  Найти первые m простых чисел p1,...,pm{\displaystyle p_{1},...,p_{m}}, где m такое, что pm≤f(n)≤pm+1{\displaystyle p_{m}\leq f(n)\leq p_{m+1}}
     Вычислить s{\displaystyle s} и q{\displaystyle q} такие, что n−1=q⋅2s{\displaystyle n-1=q\cdot 2^{s}} и q{\displaystyle q} - нечётное
     Положить i=1{\displaystyle i=1} перейти на шаг (4)
(3)  если i≤m{\displaystyle i\leq m}, то i=i+1{\displaystyle i=i+1} 
     если i>m{\displaystyle i>m}, то вернуть простое
(4)  если pi|n{\displaystyle p_{i}|n}, то вернуть составное
     Вычислить piqmodn,piq⋅2modn,...,piq⋅2smodn{\displaystyle p_{i}^{q}{\bmod {n}},p_{i}^{q\cdot 2}{\bmod {n}},...,p_{i}^{q\cdot 2^{s}}{\bmod {n}}}
(5)  если piq⋅2smodn≠1{\displaystyle p_{i}^{q\cdot 2^{s}}{\bmod {n}}\neq 1} то вернуть составное
(6)  если piqmodn=1{\displaystyle p_{i}^{q}{\bmod {n}}=1} то перейти на шаг (3)
     Положить j=max(jpiq⋅2jmodn≠1){\displaystyle j=\max(j:p_{i}^{q\cdot 2^{j}}{\bmod {n}}\neq 1)}
(7)  если piq⋅2jmodn=n−1{\displaystyle p_{i}^{q\cdot 2^{j}}{\bmod {n}}=n-1} то перейти на шаг (3)
(8)  вернуть составное

Принцип работы алгоритма

Как и тесты Ферма и Соловея — Штрассена, тест Миллера — Рабина опирается на проверку ряда равенств, которые выполняются для простых чисел. Если хотя бы одно такое равенство не выполняется, это доказывает что число составное.

Для теста Миллера — Рабина используется следующее утверждение:

Доказательство

Лемма про квадратные корни единицы в конечном поле Zp{\displaystyle \mathbb {Z} _{p}}:

Доказательство

Пусть:

a≡1(modp){\displaystyle {a}\equiv {\sqrt {1}}{\pmod {p}}}

Тогда:

a2≡1(modp){\displaystyle a^{2}\equiv 1{\pmod {p}}}
a2−1≡(modp){\displaystyle a^{2}{-1}\equiv 0{\pmod {p}}}
(a+1)(a−1)≡(modp){\displaystyle (a+1)(a-1)\equiv 0{\pmod {p}}}

По лемме Евклида:

a−1≡(modp)a+1≡(modp){\displaystyle {\bigg [}{\begin{matrix}{a-1}\equiv {0}{\pmod {p}}\\{a+1}\equiv {0}{\pmod {p}}\end{matrix}}}
a≡1(modp)a≡−1(modp){\displaystyle {\bigg [}{\begin{matrix}{a}\equiv {1}{\pmod {p}}\\{a}\equiv {-1}{\pmod {p}}\end{matrix}}}

По малой теореме Ферма:

an−1≡1(modn).{\displaystyle a^{n-1}\equiv 1{\pmod {n}}.}

Будем извлекать квадратные корни из числа an−1{\displaystyle a^{n-1}}
Используя доказанную выше лемму, на каждом шаге у нас будет получаться число 1 или -1.
Если на каком-то шаге у нас получится -1, то выполняется второе из равенств.
Иначе на очередном шаге an−12s=ad{\displaystyle {\sqrt{a^{n-1}}}=a^{d}} (т. к. n−1=2sd{\displaystyle n-1=2^{s}d}) т. е. выполнится первое равенство.
 

Если это утверждение (условие 1 или 2) выполняется для некоторых чисел a{\displaystyle a} и n{\displaystyle n} (не обязательно простого), то число a{\displaystyle a} называют свидетелем простоты числа n{\displaystyle n} по Миллеру, а само число n{\displaystyle n} — вероятно простым. (При случайно выбранном a{\displaystyle a} вероятность ошибочно принять составное число за простое составляет 25 %, но её можно уменьшить, выполнив проверки для других a{\displaystyle a}.)

В случае когда выполняется контрапозиция доказанного утверждения, то есть если найдется число a{\displaystyle a} такое, что:

ad≢1(modn){\displaystyle a^{d}\not \equiv 1{\pmod {n}}}

и

∀r ≤r≤s−1 a2rd≢−1(modn),{\displaystyle \forall r:\ 0\leq r\leq s-1:\ a^{2^{r}d}\not \equiv -1{\pmod {n}},}

то число n{\displaystyle n} не является простым. В этом случае число a{\displaystyle a} называют свидетелем того, что число n{\displaystyle n} составное.

У нечётных составных чисел n{\displaystyle n} существует, согласно теореме Рабина, не более φ(n)4{\displaystyle \varphi (n)/4} свидетелей простоты, где φ(n){\displaystyle \varphi (n)} — функция Эйлера, таким образом вероятность того, что случайно выбранное число a{\displaystyle a} окажется свидетелем простоты, меньше 1/4.

Идея теста заключается в том, чтобы проверять для случайно выбранных чисел a<n{\displaystyle a<n}, являются ли они свидетелями простоты числа n{\displaystyle n}. Если найдётся свидетель того, что число составное, то число действительно является составным. Если было проверено k{\displaystyle k} чисел, и все они оказались свидетелями простоты, то число считается простым. Для такого алгоритма вероятность принять составное число за простое будет меньше (14)k{\displaystyle (1/4)^{k}}.

Для проверки больших чисел принято выбирать числа а случайными, так как распределение свидетелей простоты и свидетелей составного числа среди чисел 1, 2, …, n − 1 заранее неизвестно. В частности Арнольт приводит 397-разрядное составное число, для которого все числа меньше 307 являются свидетелями простоты.

Алгоритм Миллера — Рабина

Реализация

Алгоритм Миллера — Рабина параметризуется количеством раундов r. Рекомендуется брать r порядка величины log2⁡(n){\displaystyle \log _{2}(n)}, где n — проверяемое число.

Для данного n находятся такие целое число s и целое нечётное число t, что n−1=2st{\displaystyle n-1=2^{s}t}. Выбирается случайное число a, 1 < a < n. Если a не является свидетелем простоты числа n, то выдаётся ответ «n — составное», и алгоритм завершается. Иначе, выбирается новое случайное число a и процедура проверки повторяется. После нахождения r свидетелей простоты, выдаётся ответ «n — вероятно простое», и алгоритм завершается.

Алгоритм может быть записан на псевдокоде следующим образом:

 Ввод: n > 2, нечётное натуральное число, которое необходимо проверить на простоту;
       k — количество раундов.
Вывод: составное, означает, что n является составным числом;
       вероятно простое, означает, что n с высокой вероятностью является простым числом.
Представить n − 1 в виде 2s·t, где t нечётно, можно сделать последовательным делением n - 1 на 2.
цикл А: повторить k раз:
   Выбрать случайное целое число a в отрезке [2, n − 2]
   xat mod n, вычисляется с помощью алгоритма возведения в степень по модулю
   если x = 1 или x = n − 1, то перейти на следующую итерацию цикла А
   цикл B: повторить s − 1 раз
      xx2 mod n
      если x = 1, то вернуть составное
      если x = n − 1, то перейти на следующую итерацию цикла A
   вернуть составное
вернуть вероятно простое

Из теоремы Рабина следует, что если k случайно выбранных чисел оказались свидетелями простоты числа n, то вероятность того, что n составное, не превосходит 4−k{\displaystyle 4^{-k}}.

Также для больших значений n вероятность объявления составного числа вероятно простым существенно меньше чем 4−k. Дамгард, Лэндрок и Померандс вычислили некоторые точные границы границы ошибок и предложили метод выбора значения k для получения нужной границы ошибки. Такие границы могут, например, использоваться для генерации вероятно простых чисел. Однако, они не должны использоваться для проверки простых чисел неизвестного происхождения, поскольку в криптографических системах взломщик может попытаться подставить псевдопростое число, в той ситуации когда требуется простое число. В таких случаях можно положиться только на ошибку 4−k.

Сложность работы

Считая, что время умножения логарифмическое, используя , сложность работы алгоритма O(klog3⁡n){\displaystyle O(k\log ^{3}n)}, где k{\displaystyle k} — количество раундов. Таким образом, время работы алгоритма полиномиально.

Однако, используя БПФ, возможно сократить время работы алгоритма до O(klog2⁡(n)log⁡(log⁡(n))log⁡(log⁡(log⁡(n))))=O(klog2⁡(n)){\displaystyle O(k\log ^{2}(n)\log(\log(n))\log(\log(\log(n))))=O(k\log ^{2}(n))}. В таком случае, если брать k=log2⁡(n){\displaystyle k=\log _{2}(n)}, где n — проверяемое число, то сложность работы алгоритма равна O(log3⁡n){\displaystyle O(\log ^{3}n)}.

Сигналы и объем

Как с годами меняется мозг ребенка? И, в частности, какие процессы происходят с рабочей памятью? Чтобы ответить на эти вопросы, на протяжении последних нескольких лет моя группа в составе Хелены Вестерберг, Перниллы Олесен и Ханса Форссберга проводила исследования в Каролинском институте в Стокгольме. Мы использовали простые тесты — дети запоминали конфигурацию из точек, и пока они решали эту задачу, мы сканировали их мозг55. Мы обнаружили, что у детей увеличилась активность специфических областей мозга — в теменной доле, а также в верхней и в передней части лобной доли. Наши выводы во многом совпали с выводами других ученых, которые ранее проводили аналогичные исследования.

Теменная доля — довольно большая зона мозга. В медицине ее называют париетальной долей. Кора мозга в париетальной доле состоит из складок и образует ин- трапариетальную борозду (sulcus intraparietalis). Самые явные изменения мы наблюдали именно в коре головного мозга, как раз в этих складках

И именно в этом сегменте наши коллеги обнаружили очаг активности при выполнении задач на произвольное внимание

Причем в зависимости от характера задач активность мозга в лобной доле менялась. Эти выводы подтвердили многие исследования

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

Есть еще один способ определить ключевые структуры, ответственные за объем рабочей памяти. Вспомним кривую, приведенную во вступительной главе, которая обозначает пределы рабочей памяти.

В 2004 году журнал «Природа» («Nature») опубликовал результаты двух исследований рабочей памяти57. Участникам первого исследования предъявляли сначала 2, затем 4,6 и наконец 8 объектов. В роли объектов выступали маленькие круги, причем следовало запомнить и цвет круга, и место его расположения на экране. Постепенно результаты становились все хуже, точно так, как это показывает диаграмма. Затем активность мозга измерили с помощью функционального магнит- но-резонансного томографа. Оказалось, что функционировала только одна-единственная область, как показано на диаграмме — в интрапариетальной борозде. В аналогичном опыте электрическую активность анализировали уже с помощью электроэнцефалограммы (ЭЭГ), и опять, как и на диаграмме, активизировалась область в интрапариетальной борозде58.

Как же тогда обстоят дела с интеллектуальными способностями, ведь считалось, что они связаны с объемом рабочей памяти? В фундаментальном исследовании под руководством южнокорейского ученого Кун Хо Ли интеллект молодых людей измерили по матрицам Равена, а затем просканировали их мозг, когда они решали задачи на запоминание59. Исследователи обнаружили, что чем лучше испытуемые справлялись с заданиями, тем выше была активность мозга в лобной и в теменной долях, и особенно в интрапариетальной борозде теменной доли60. Именно эта зона мозга отвечает за развитие рабочей памяти в детстве, и этот факт подтвержден исследованиями моей группы, а также другими учеными.

Многие исследования подтверждают, что области в теменной и лобной долях напрямую связаны с объемом нашей рабочей памяти. То есть не весь мозг, а лишь некоторые его сегменты участвуют в деятельности рабочей памяти

Причем это те самые области, которые, как мы уже знаем, активируются, когда рабочая память сохраняет полученную информацию и когда внимание направляется на заранее намеченную цель. Может быть, здесь и находятся ключевые структуры, или то «узкое бутылочное горло», которое ограничивает нашу способность воспринимать и сохранять информацию? То, что к этому причастна лобная доля, кстати, вполне объяснимо, поскольку многие исследования последних десятилетий доказывают: лобная доля непосредственно отвечает за наши активные когнитивные функции

Но о том, что теменная доля также играет важную роль в этом процессе, стало известно сравнительно недавно. Примечательно и то, что роль теменной доли однозначно подчеркивают различные исследования, использующие разные методы.

Может быть, не случайно мозг Эйнштейна выделяется именно развитостью теменных долей. Мозг Эйнштейна вполне обычен по весу и размеру, и по большинству своих характеристик является вполне «среднестатистическим». А вот теменная часть гораздо шире, чем у «обычных» людей61. К тому же левая теменная доля оказалась намного больше правой

Еще одна особенность, обратившая на себя внимание ученых, — борозда, разделяющая височную и теменную доли, чрезмерно увеличена и смещена вперед, что и объясняет расширение теменной доли

Время работы

В случае, когда верхняя граница перебора задаётся функцией f(n)=2⋅ln2(n){\displaystyle f(n)=2\cdot ln^{2}(n)}, алгоритм детерминировано проверяет простоту числа n за O(ln4(n)){\displaystyle O(ln^{4}(n))}

, но при этом алгоритм опирается на обобщенную гипотезу Римана. Проще говоря, трудоемкость алгоритма растет быстрее чем O(ln2(n)){\displaystyle O(ln^{2}(n))}, (количество проверяемых оснований на псевдопростоту), потому что чем больше основание, тем более трудоемкий его анализ. От размера (меры) входных данных: длины записи m{\displaystyle m}, проверяемого числа n{\displaystyle n} , трудоёмкость алгоритма значит, зависит так: O(m4){\displaystyle O(m^{4})}, то есть полиномиальная сложность, 4-й степени.

В случае, когда f(n)=n0.133{\displaystyle f(n)=n^{0.133}}, время работы в худшем случае оценивается как O(n17){\displaystyle O(n^{1/7})} .
От размера входных данных: длины записи m{\displaystyle m}, проверяемого числа n{\displaystyle n}, трудоёмкость алгоритма, зависит так: O(em7){\displaystyle O(e^{m/7})}, то есть экспоненциальная сложность степени 1/7. Этот алгоритм намного сложнее: все экспоненциальные алгоритмы, при достаточно большом размере входных данных, становятся существенно более трудоемкими, чем все полиномиальные алгоритмы.

Сильно псевдопростые числа

Если число a является свидетелем простоты составного нечётного числа n по Миллеру, то число n, в свою очередь, называется сильно псевдопростым по основанию a. Если число n является сильно псевдопростым по основанию a, то оно также является псевдопростым Ферма по основанию a, так и по основанию a.

Например, сильно псевдопростые числа по основанию 2 образуют последовательность:

2047, 3277, 4033, 4681, 8321, 15841, 29341, 42799, 49141, 52633, 65281, 74665, … (последовательность A001262 в OEIS)

а по основанию 3 — последовательность:

121, 703, 1891, 3281, 8401, 8911, 10585, 12403, 16531, 18721, 19345, 23521, 31621, … (последовательность A020229 в OEIS)

Время работы

В случае, когда верхняя граница перебора задаётся функцией f(n)=2⋅ln2(n){\displaystyle f(n)=2\cdot ln^{2}(n)}, алгоритм детерминировано проверяет простоту числа n за O(ln4(n)){\displaystyle O(ln^{4}(n))}

, но при этом алгоритм опирается на обобщенную гипотезу Римана. Проще говоря, трудоемкость алгоритма растет быстрее чем O(ln2(n)){\displaystyle O(ln^{2}(n))}, (количество проверяемых оснований на псевдопростоту), потому что чем больше основание, тем более трудоемкий его анализ. От размера (меры) входных данных: длины записи m{\displaystyle m}, проверяемого числа n{\displaystyle n} , трудоёмкость алгоритма значит, зависит так: O(m4){\displaystyle O(m^{4})}, то есть полиномиальная сложность, 4-й степени.

В случае, когда f(n)=n0.133{\displaystyle f(n)=n^{0.133}}, время работы в худшем случае оценивается как O(n17){\displaystyle O(n^{1/7})} .
От размера входных данных: длины записи m{\displaystyle m}, проверяемого числа n{\displaystyle n}, трудоёмкость алгоритма, зависит так: O(em7){\displaystyle O(e^{m/7})}, то есть экспоненциальная сложность степени 1/7. Этот алгоритм намного сложнее: все экспоненциальные алгоритмы, при достаточно большом размере входных данных, становятся существенно более трудоемкими, чем все полиномиальные алгоритмы.

Пример реализации алгоритма.[ | код]

Пример реализации алгоритма, на языке программирования C# (.NET Framework 3.5, 4.0).

Это только один из примеров, и переменную maxChecked, можно определить по другому, и по формуле 2⋅ln2(n){\displaystyle 2\cdot ln^{2}(n)} от проверяемого числа (классический тест Миллера), и по точным значениям для чисел, меньших чем 1036{\displaystyle 10^{36}}, описанным выше, и вообще произвольным образом так, что получится реализация алгоритма Миллера-Рабина.

public bool IsPrime_AlgMiller(BigInteger checkingNum)
{
    // (если является степенью другого числа)
    if (IsPowerOfNumber(checkingNum))
        return false;

    BigInteger logN = new BigInteger(BigInteger.Log(checkingNum));
    BigInteger loglogN = new BigInteger(BigInteger.Log(logN));
    BigInteger maxChecked = logN * loglogN  (new BigInteger(BigInteger.Log(2)));

    // maxChecked: получено максимальное основание, для проверки на сильную псевддопростоту. Это один из примеров.
    BigInteger baseCurrent = new BigInteger(2);

    bool isPrime = true;

    while (baseCurrent <= maxChecked)
    {
        // (если не сильно псевдопростое по этому основанию)
        if (! IsStrongPseudoPrime(checkingNum, baseCurrent))
        {
           // (тогда число не простое)
           isPrime = false;
           break;
        }

        baseCurrent = NextPrime(baseCurrent);
    }

    return isPrime;
}


public bool IsStrongPseudoPrime(BigInteger checkingNum, BigInteger baseCurrent)
{
    BigInteger exp = checkingNum - 1;

    // (exp будет меняться, а проверка остатка -1 эквивалентна проверке остатка (checkingNum - 1))
    BigInteger ost_1 = exp;

    BigInteger res = BigInteger.ModPow(baseCurrent, exp, checkingNum);

    if (res != 1)
        return false;

    // (тест Ферма пройден)

    while (true)
    {
        // (чётное; при первом попадании всегда будет чётным, далее цикл до тех пор пока снова станет нечётным)
        exp = exp  2;

        // (остаток -1 всегда должны проверить)
        res = BigInteger.ModPow(baseCurrent, exp, checkingNum);

        if (res == ost_1)
            return true;

        // (снова стало нечётным — нужно проверить ещё на 1)
        if (!exp.IsEven)
        {
            res = BigInteger.ModPow(baseCurrent, exp, checkingNum);
            if (res.IsOne)
               return true;

            break;
        }
    }

    return false;
}


public BigInteger NextPrime(BigInteger num)
{
    //...
}

public bool IsPowerOfNumber(BigInteger n)
// n=p^k => p-1|n-1 && 2^(p-1)=1(mod p) => 2^(n-1)=1(mod p) => p|2^(n-1)-1 => p|gcd(2^(n-1)-1,n)
{
    return BigInteger.GreatestCommonDivisor(BigInteger.ModPow(2, n - 1, n)-1, n)>1;
}

Остаётся реализовать только две функции —

1) NextPrime, которая получает число, и возвращает следующее за ним, простое число, оптимальная для нахождения именно малых простых чисел. Эта функция должна быть ещё проще и быстрее.

2) IsPowerOfNumber — функция, немного более сложная, которая определяет, является ли передаваемое число, степенью другого, простого числа. Нужно найти максимально простую реализацию этой функции.

Также,

3) Можно ускорить реализацию алгоритма, отсеяв вначале, случаи, когда число делится на возможные малые делители, но часто-встречающиеся делители: 2,3,5,7,11… и только потом выполнять основную проверку по алгоритму Миллера.

В таком случае реализация алгоритма Миллера в предложенном примере, скорее всего, будет наибыстрейшей, для произвольных больших чисел. А сам алгоритм, как уже показано выше, претендует на звание самого быстрого достоверного алгоритма на проверку простоты (если верна обобщённая гипотеза Римана).

Эта реализация алгоритма была успешно протестирована и без использования функции IsPowerOfNumber.

Описание принципа

Что такое Число Миллера простыми словами?

Число Миллера (или 7 ± 2) — это “концепция”, согласно которой человек может хранить в краткосрочной памяти не больше 7 ± 2 сущностей за раз. Каждый раз когда объекты переполняют резервуар: что-то перезаписывается.

«Магическое число семь плюс-минус два» («кошелёк Миллера», «закон Миллера») — закономерность, обнаруженная американским учёным-психологом Джорджем Миллером, согласно которой кратковременная человеческая память, как правило, не может запомнить и повторить более 7 ± 2 элементов. Википедия.

Как эта информация реализуется в практической плоскости:

️ Вы можете фокусироваться на определенном количестве целей/направлений развития за раз.

Значит ваш резерв внимания весьма ограничен

Отсюда и следуют необходимость концентрации и деление на: важно и не важно для вашей цели. Как следствие, концентрация внимания, приоритизация, и способность сказать “нет”— ключевые компетенции

Это достаточно практичная информация для людей, которые нацелены на саморазвитие и проработку своих сторон. По неопытности, по юности, можно слепо уповать максимализму и ставить слишком много задач одновременно. Как следствие, обс#р@ться по всем, или достигать немного.

Короткий ответ, практические рекомендации как применить это знание:

  • Ставить разумное кол-во объектов на “полку внимания”, не позволять себе жадно планировать то, что невозможно будет реализовать из-за ограниченности ресурсов. Золотое правило: максимум 3 больших задачи за раз, 1 из которых главная;
  • Регулярно выгружать информацию из головы в приложения для заметок, или классический блокнот, мб Evernote, Trello, на ваш вкус;
  • Помнить о правило “зона забот” и “зона влияния”. “Зона Забот” — это то, что парит но нельзя поменять никак. “Зона влияния” — это то, что можно изменить приложив определенные концентрированные усилия. Отсюда опять же, вывод – не загружать свой “бортовой компьютер” всякой х.

Пример реализации алгоритма.

Пример реализации алгоритма, на языке программирования C# (.NET Framework 3.5, 4.0).

Это только один из примеров, и переменную maxChecked, можно определить по другому, и по формуле 2⋅ln2(n){\displaystyle 2\cdot ln^{2}(n)} от проверяемого числа (классический тест Миллера), и по точным значениям для чисел, меньших чем 1036{\displaystyle 10^{36}}, описанным выше, и вообще произвольным образом так, что получится реализация алгоритма Миллера-Рабина.

public bool IsPrime_AlgMiller(BigInteger checkingNum)
{
    // (если является степенью другого числа)
    if (IsPowerOfNumber(checkingNum))
        return false;

    BigInteger logN = new BigInteger(BigInteger.Log(checkingNum));
    BigInteger loglogN = new BigInteger(BigInteger.Log(logN));
    BigInteger maxChecked = logN * loglogN  (new BigInteger(BigInteger.Log(2)));

    // maxChecked: получено максимальное основание, для проверки на сильную псевддопростоту. Это один из примеров.
    BigInteger baseCurrent = new BigInteger(2);

    bool isPrime = true;

    while (baseCurrent <= maxChecked)
    {
        // (если не сильно псевдопростое по этому основанию)
        if (! IsStrongPseudoPrime(checkingNum, baseCurrent))
        {
           // (тогда число не простое)
           isPrime = false;
           break;
        }

        baseCurrent = NextPrime(baseCurrent);
    }

    return isPrime;
}


public bool IsStrongPseudoPrime(BigInteger checkingNum, BigInteger baseCurrent)
{
    BigInteger exp = checkingNum - 1;

    // (exp будет меняться, а проверка остатка -1 эквивалентна проверке остатка (checkingNum - 1))
    BigInteger ost_1 = exp;

    BigInteger res = BigInteger.ModPow(baseCurrent, exp, checkingNum);

    if (res != 1)
        return false;

    // (тест Ферма пройден)

    while (true)
    {
        // (четное; при первом попадании всегда будет четным, далее цикл до тех пор пока снова станет нечетным)
        exp = exp  2;

        // (остаток -1 всегда должны проверить)
        res = BigInteger.ModPow(baseCurrent, exp, checkingNum);

        if (res == ost_1)
            return true;

        // (снова стало нечетным — нужно проверить еще на 1)
        if (!exp.IsEven)
        {
            res = BigInteger.ModPow(baseCurrent, exp, checkingNum);
            if (res.IsOne)
               return true;

            break;
        }
    }

    return false;
}


public BigInteger NextPrime(BigInteger num)
{
    //...
}

public bool IsPowerOfNumberBigInteger n)
// n=p^k => p-1|n-1 && 2^(p-1)=1(mod p) => 2^(n-1)=1(mod p) => p|2^(n-1)-1 => p|gcd(2^(n-1)-1,n)
{
    return BigInteger.GreatestCommonDivisor(BigInteger.ModPow(2, n - 1, n)-1, n)>1;
}

Остается реализовать только две функции —

1) NextPrime, которая получает число, и возвращает следующее за ним, простое число, оптимальная для нахождения именно малых простых чисел. Эта функция должна быть еще проще и быстрее.

2) IsPowerOfNumber — функция, немного более сложная, которая определяет, является ли передаваемое число, степенью другого, простого числа. Нужно найти максимально простую реализацию этой функции.

Также,

3) Можно ускорить реализацию алгоритма, отсеяв вначале, случаи, когда число делится на возможные малые делители, но часто-встречающиеся делители: 2,3,5,7,11… и только потом выполнять основную проверку по алгоритму Миллера.

В таком случае реализация алгоритма Миллера в предложенном примере, скорее всего, будет наибыстрейшей, для произвольных больших чисел. А сам алгоритм, как уже показано выше, претендует на звание самого быстрого достоверного алгоритма на проверку простоты (если верна обобщенная гипотеза Римана).

Эта реализация алгоритма была успешно протестирована и без использования функции IsPowerOfNumber.

Алгоритм Миллера — Рабина

Реализация

Алгоритм Миллера — Рабина параметризуется количеством раундов r. Рекомендуется брать r порядка величины log2⁡(n){\displaystyle \log _{2}(n)}, где n — проверяемое число.

Для данного n находятся такие целое число s и целое нечётное число t, что n−1=2st{\displaystyle n-1=2^{s}t}. Выбирается случайное число a, 1 < a < n. Если a не является свидетелем простоты числа n, то выдаётся ответ «n — составное», и алгоритм завершается. Иначе, выбирается новое случайное число a и процедура проверки повторяется. После нахождения r свидетелей простоты, выдаётся ответ «n — вероятно простое», и алгоритм завершается.

Алгоритм может быть записан на псевдокоде следующим образом:

 Ввод: n > 2, нечётное натуральное число, которое необходимо проверить на простоту;
       k — количество раундов.
Вывод: составное, означает, что n является составным числом;
       вероятно простое, означает, что n с высокой вероятностью является простым числом.
Представить n − 1 в виде 2s·t, где t нечётно, можно сделать последовательным делением n - 1 на 2.
цикл А: повторить k раз:
   Выбрать случайное целое число a в отрезке [2, n − 2]
   xat mod n, вычисляется с помощью алгоритма возведения в степень по модулю
   если x = 1 или x = n − 1, то перейти на следующую итерацию цикла А
   цикл B: повторить s − 1 раз
      xx2 mod n
      если x = 1, то вернуть составное
      если x = n − 1, то перейти на следующую итерацию цикла A
   вернуть составное
вернуть вероятно простое

Из теоремы Рабина следует, что если k случайно выбранных чисел оказались свидетелями простоты числа n, то вероятность того, что n составное, не превосходит 4−k{\displaystyle 4^{-k}}.

Также для больших значений n вероятность объявления составного числа вероятно простым существенно меньше чем 4−k. Дамгард, Лэндрок и Померандс вычислили некоторые точные границы границы ошибок и предложили метод выбора значения k для получения нужной границы ошибки. Такие границы могут, например, использоваться для генерации вероятно простых чисел. Однако, они не должны использоваться для проверки простых чисел неизвестного происхождения, поскольку в криптографических системах взломщик может попытаться подставить псевдопростое число, в той ситуации когда требуется простое число. В таких случаях можно положиться только на ошибку 4−k.

Сложность работы

Считая, что время умножения логарифмическое, используя , сложность работы алгоритма O(klog3⁡n){\displaystyle O(k\log ^{3}n)}, где k{\displaystyle k} — количество раундов. Таким образом, время работы алгоритма полиномиально.

Однако, используя БПФ, возможно сократить время работы алгоритма до O(klog2⁡(n)log⁡(log⁡(n))log⁡(log⁡(log⁡(n))))=O(klog2⁡(n)){\displaystyle O(k\log ^{2}(n)\log(\log(n))\log(\log(\log(n))))=O(k\log ^{2}(n))}. В таком случае, если брать k=log2⁡(n){\displaystyle k=\log _{2}(n)}, где n — проверяемое число, то сложность работы алгоритма равна O(log3⁡n){\displaystyle O(\log ^{3}n)}.

Время работы

В случае, когда верхняя граница перебора задаётся функцией f(n)=2⋅ln2(n){\displaystyle f(n)=2\cdot ln^{2}(n)}, алгоритм детерминировано проверяет простоту числа n за O(ln4(n)){\displaystyle O(ln^{4}(n))}

, но при этом алгоритм опирается на обобщённую гипотезу Римана. Проще говоря, трудоёмкость алгоритма растёт быстрее чем O(ln2(n)){\displaystyle O(ln^{2}(n))}, (количество проверяемых оснований на псевдопростоту), потому что чем больше основание, тем более трудоёмкий его анализ. От размера (меры) входных данных: длины записи m{\displaystyle m}, проверяемого числа n{\displaystyle n} , трудоёмкость алгоритма значит, зависит так: O(m4){\displaystyle O(m^{4})}, то есть полиномиальная сложность, 4-й степени.

В случае, когда f(n)=n0.133{\displaystyle f(n)=n^{0.133}}, время работы в худшем случае оценивается как O(n17){\displaystyle O(n^{1/7})} .
От размера входных данных: длины записи m{\displaystyle m}, проверяемого числа n{\displaystyle n}, трудоёмкость алгоритма, зависит так: O(em7){\displaystyle O(e^{m/7})}, то есть экспоненциальная сложность степени 1/7. Этот алгоритм намного сложнее: все экспоненциальные алгоритмы, при достаточно большом размере входных данных, становятся существенно более трудоёмкими, чем все полиномиальные алгоритмы.

Понравилась статья? Поделиться с друзьями:
Психея
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!:
Нажимая на кнопку "Отправить комментарий", я даю согласие на обработку персональных данных и принимаю политику конфиденциальности.