Введение: проверка простоты и факторизация — разные задачи
Проверка простоты отвечает, является ли целое число простым. Поиск самих множителей составного числа — другая задача. Генерация больших простых чисел важна для криптографии с открытым ключом, включая RSA, однако само умножение не является трудной операцией. Наличие простых чисел также не гарантирует безопасность всей криптографической схемы.
Мы сравним пробные деления, тест Ферма и тест Миллера–Рабина, затем приведём реализацию на C++ для неотрицательных 64-битных целых чисел. Её область — \(0\le N<2^{64}\), а не числа длиной в тысячи бит, используемые при генерации ключей RSA. Время зависит от реализации и входных данных; универсальных гарантий секунд или кратности ускорения нет.
1. Простые числа и пробные деления
Простое число — целое число не меньше 2, положительные делители которого только 1 и оно само. Составное число — целое число не меньше 2, которое не является простым. Ноль, единица и отрицательные целые числа не являются ни простыми, ни составными.
Если составное \(N=ab\) имеет множители с \(2\le a\le b\), то \(a\le\sqrt N\). Поэтому достаточно искать делители до квадратного корня. Сначала проверяется 2, затем только нечётные кандидаты.
| |
Знаковое умножение в i*i <= n может переполниться. Для положительных значений эквивалентное условие i <= n/i позволяет не вычислять это произведение. Функция принимает значения диапазона std::int64_t и возвращает false также для отрицательных входов.
Число делений в худшем случае составляет \(\mathcal{O}(\sqrt N)\). Проверка только нечётных значений даёт примерно \(\sqrt N/2\) кандидатов, но многократные тесты больших чисел всё равно могут быть затратны. Универсальной границы практичности по числу десятичных цифр нет.
2. Тест Ферма и возведение в степень по модулю
Малая теорема Ферма утверждает, что для простого \(p\) и целого \(a\) с \(\gcd(a,p)=1\):
\[ a^{p-1}\equiv1\pmod p. \]Поэтому для \(N>2\) основание \(1<a<N-1\), нарушающее это сравнение, доказывает составность \(N\). Успешная проверка не доказывает простоту. Число Кармайкла \(561=3\cdot11\cdot17\) проходит тест для всех оснований, взаимно простых с 561. Но основание 3 не взаимно просто с 561, и \(3^{560}\bmod561=375\), поэтому проверка завершается неуспехом. Неверно говорить, что любое основание обязательно обманывает тест.
Не нужно сначала строить полную целочисленную степень. Бинарное возведение в степень берёт остаток после каждого умножения. Следующие вспомогательные функции можно компилировать отдельно. Основание, показатель и операнды умножения — неотрицательные 64-битные целые числа; модуль m — положительное 64-битное целое. При нулевом модуле выбрасывается исключение.
| |
Результат — наименьший неотрицательный остаток. При нулевом показателе возвращается \(1\bmod m\), то есть ноль для модуля 1. Здесь \(0^0\) также обрабатывается по этому вычислительному соглашению. Передача отрицательного основания непосредственно в беззнаковый параметр не входит в предполагаемый интерфейс.
unsigned __int128 — расширение, а не тип стандарта ISO C++. GCC поддерживает его на подходящих целевых платформах; отсутствие поддержки обнаруживается при компиляции. Не предполагается, что код без изменений работает в любой среде MSVC; см. документацию GCC. Для представления всех произведений двух беззнаковых 64-битных значений нужно беззнаковое 128-битное хранение: знаковый 128-битный тип не охватывает все такие произведения.
3. Условия Миллера–Рабина и смысл вероятности
По модулю нечётного простого числа квадратными корнями из 1 являются только 1 и −1. Тест Миллера–Рабина сочетает это свойство с малой теоремой Ферма. Для нечётного \(N\ge3\) запишем:
\[ N-1=d\,2^s,\qquad d\text{ is odd},\quad s\ge1. \]Здесь d нечётно. Основание \(a\) с \(1<a<N-1\) проходит раунд, если выполняется одно из условий:
\[ a^d\equiv1\pmod N \quad\text{or}\quad \exists r\ (0\le r<s):\ a^{d2^r}\equiv-1\pmod N. \]Простое число всегда проходит; неудача доказывает составность. Если последовательное возведение в квадрат даёт 1, хотя предыдущее значение не было ни 1, ни −1, это тоже доказывает составность. Наименьший неотрицательный остаток для −1 равен \(N-1\), но знаковое выражение C++ -1 % N не возвращает это значение. Например, -1 % 7 равно −1. Нужно различать математический остаток и правила деления и остатка в C++.
Для фиксированного нечётного составного числа \(N\) каждое основание, выбранное независимо и равномерно из \(2\le a\le N-2\), проходит с вероятностью не более \(1/4\). Поэтому вероятность ошибочного прохождения всех \(k\) раундов ограничена так:
\[ \Pr[\text{all }k\text{ rounds pass}\mid N\text{ is composite}]\le4^{-k}. \]Это вероятность ошибки относительно случайного выбора оснований. Её нельзя непосредственно переформулировать как вероятность \(4^{-k}\) того, что число составное после успешного теста. Аргумент также не применим к произвольному фиксированному набору оснований. Для N=3 интервал оснований пуст, поэтому реализация обрабатывает этот случай заранее. Оригинальная работа Рабина также различает вероятностное принятие и доказательство простоты.
Схема показывает один раунд для обычного основания. N=3 уже обработано; для нечётного входа должно существовать основание в указанном интервале.
graph TD
Start["Один раунд: N нечётно и не меньше 3; основание от 2 до N-2"] --> DS["N-1 = d*2^s; d нечётно"]
DS --> Power["x = a^d mod N"]
Power --> First{"x равно 1 или N-1?"}
First -- Да --> Pass["Это основание пройдено: не доказательство простоты"]
First -- Нет --> Init["Положить r=1"]
Init --> More{"r меньше s?"}
More -- Нет --> Fail["Составное"]
More -- Да --> Square["x = x*x mod N"]
Square --> Minus{"x равно N-1?"}
Minus -- Да --> Pass
Minus -- Нет --> One{"x равно 1?"}
One -- Да --> Fail
One -- Нет --> Next["Увеличить r на 1"]
Next --> More
4. Детерминированная проверка в фиксированном диапазоне
В ограниченном диапазоне целых чисел достаточный набор оснований, подкреплённый известными результатами проверки, позволяет обходиться без случайности. Записи оснований SPRP приводят используемые здесь условия. Все верхние границы строгие.
| Входной диапазон | Проверяемые основания |
|---|---|
| \(N<4759123141\) | 2, 7, 61 |
| \(N<1122004669633\) | 2, 13, 23, 1662803 |
| \(N<2^{64}\) | 2, 325, 9375, 28178, 450775, 9780504, 1795265022 |
Последний набор содержит семь целых оснований, а не семь простых чисел: например,325 составное. В записи результат с семью основаниями приписан Jim Sinclair в 2011 году. Эта статья не воспроизводит независимо весь поиск на 64-битном диапазоне.
Альтернатива — первые двенадцать простых чисел 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, которых также достаточно для этого 64-битного диапазона. Результат Sorenson–Webster даёт 318665857834031151167461 как наименьшее составное число, которое является сильным псевдопростым по всем двенадцати основаниям; оно больше \(2^{64}\). Следующая реализация использует ветви с 3, 4 и 7 основаниями из таблицы, а не вариант с двенадцатью.
5. Полный пример на C++
Входной тип u64 охватывает \(0\le N\le2^{64}-1\). Ноль и единица дают false, но не являются составными, поэтому вывод использует Not prime. При умножении по модулю преобразование типа происходит до вычисления произведения. Каждое основание заменяется остатком по модулю N; если получается ноль, пропускается только это основание, а остальные проверяются дальше. Нулевое основание само по себе не определяет простоту или составность всего числа.
Следующий блок — полная программа со вспомогательными функциями. Предыдущие блоки не нужно дополнительно присоединять к ней. На подходящей целевой платформе GCC можно скомпилировать командой вроде g++ -std=c++17 -O2 primality.cpp -o primality.
| |
Вывод классифицирует 0 и 1 как Not prime, 2 и 73 как Prime, 561 как Not prime, 1000000007,998244353 и 1000000000000000003 как Prime, 1000000000000000007 как Not prime, 18446744073709551557 как Prime, а 18446744073709551615 как Not prime. Последнее составное число равно \(2^{64}-1\). Это учебный пример и проверка обычных целых в указанном диапазоне, а не криптографический генератор простых чисел, реализация с постоянным временем выполнения или библиотека проверки входных строк.
6. Сложность и умножение Монтгомери
Если считать одно умножение по модулю одной операцией, бинарное возведение в степень и раунд Миллера–Рабина требуют \(\mathcal{O}(\log N)\) операций; для k оснований требуется \(\mathcal{O}(k\log N)\). Метод пробных делений выполняет \(\mathcal{O}(\sqrt N)\) делений. Через длину входа \(b=\lfloor\log_2 N\rfloor+1\) число умножений по модулю равно \(\mathcal{O}(kb)\). Для целых произвольной точности в битовую сложность нужно включать также стоимость умножения и вычисления остатка.
В этом фиксированном диапазоне нужно не более семи оснований. Однако на один бит, помимо квадрата, может приходиться условное умножение, а последующая цепочка квадратов добавляет операции. Поэтому \(7\times64=448\) не ограничивает все элементарные операции. При сравнении времени следует указывать CPU, компилятор, параметры оптимизации, набор входов и метод измерения.
Умножение Монтгомери меняет представление, чтобы избегать деления на модуль при повторных операциях с одним модулем. В обычной конструкции основание системы счисления является степенью двойки; тогда модуль должен быть нечётным и взаимно простым с этим основанием. Помимо умножений и сдвигов, нужны сложения, обработка переносов и условные вычитания; преобразование и предварительные вычисления тоже имеют стоимость. Ускорение вдвое или втрое не гарантируется. Процедуры и условия см. в HAC, глава 14, алгоритмы 14.32, 14.36 и 14.94. В приведённом коде этот метод не реализован.
7. Заключение
Пробные деления непосредственно ищут делители, а тест Ферма отбрасывает кандидатов с помощью сравнения по модулю. Миллер–Рабин использует более сильные условия. Общую вероятностную форму следует отличать от детерминированного теста с проверенным набором оснований и ограниченным диапазоном входов.
graph LR
Trial["Пробные деления: искать делители"] --> Fermat["Ферма: отбрасывать по сравнению"]
Fermat --> MR["Миллер-Рабин: более сильные условия"]
MR --> Fixed["Проверенные основания и фиксированный диапазон: детерминированный тест"]
MR --> Mont["Монтгомери: улучшать реализацию арифметики по модулю"]
Корректность зависит не только от математики, но и от диапазона входов, целых типов, переполнения, условий на модуль и обработки оснований. Гарантии 64-битного кода не распространяются на целые произвольной длины или на криптографическую систему целиком.
Источники
- HAC, глава 4: Ферма, Миллер–Рабин и условия вероятности ошибки (источник 1996 года; отличать последующие результаты и современные требования к генерации ключей)
- Rabin (1980): Probabilistic Algorithm for Testing Primality
- Записи оснований SPRP для детерминированного Миллера–Рабина
- Sorenson–Webster: Strong Pseudoprimes to Twelve Prime Bases (препринт 2015 года, журнальная публикация 2017 года)
- GCC: расширение 128-битных целых
- Рабочий проект C++: деление и остаток
- HAC, глава 14: арифметика Монтгомери
