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

Удивительно, но мне удалось очень быстро найти такие закономерности самостоятельно. Я обнаружил, что если взять ряд всех натуральных чисел, то есть все целые числа от единицы и больше, и разбить его на группы по 30 чисел, а затем расположить эти группы в виде строк таблицы одну под другой, то все простые числа будут расположены в столбцах этой таблицы с определёнными номерами. Исключением из этого правила являются лишь три первых простых числа: 2, 3 и 5, произведение которых между собой как раз равно 30. Другими словами, взяв любое число, можно с уверенностью сказать, может ли это число быть простым, в этом случае оно будет давать один из определённых остатков при делении на 30 — это и будет номер столбца в нашей таблице, или оно простым совершенно точно не является, оно — составное.

Долгое время я пребывал в наивной уверенности, что обладаю тайным знанием, а также в скромных мечтах, что однажды настанет день, когда мне получится поделиться им с человечеством. Конечно, всегда находились более насущные и срочные дела, пока несколько лет назад я не решил взяться за факторизацию. Честно говоря, я был довольно разочарован, когда я узнал, что моё открытие, оказывается, давно известно математикам. Как же я был наивен тогда! То, что я обнаружил тогда по наитию, легко объяснимо — любое простое число, большее 5, по определению не может делиться ни на 2, ни на 3, ни на 5. Следовательно, оно является взаимно простым с 30. Количество натуральных взаимно простых чисел с заданным числом n, меньших или равных ему, равно значению функции Эйлера для этого числа: \varphi(n). Число 30 имеет \varphi(30)=8 взаимно простых с ним чисел. Это означает, что все простые числа больше 5 будут давать один из следующих остатков при делении на 30:

\mathcal{R}_{30} = \{1, 7, 11, 13, 17, 19, 23, 29\}

Это и есть номера столбцов в нашей таблице, содержащих все простые числа больше 5.

Итак, факторизация. Основная теорема арифметики гласит, что любое составное число можно разложить на простые сомножители, причём единственным способом. Под способом здесь подразумевается то, что получившийся набор простых сомножителей уникален для любого целого числа. Операция разложения чисел на сомножители называется факторизацией. Довольно просто, не правда ли? Но при всей простоте формулировки мы пока не знаем формулы, которую можно было бы применить для решения этой задачи. Поскольку формулы нет, будем говорить об алгоритмах, которые математики исторически называют методами. Все известные (по крайней мере мне) методы факторизации основаны на той или иной форме перебора параметров, ведущей в итоге к решению.

Самым элементарным и наивным алгоритмом является простой перебор чисел в попытках найти число, которое будет делить заданное число без остатка. Если для небольших чисел мы с лёгкостью можем найти все его сомножители (или делители — кому как нравится) в уме, то чем большее число мы берём, тем эта операция становится сложнее. В итоге она становится сложной настолько, что требуются сотни, тысячи, а может и миллионы лет, в зависимости от размерности числа, чтобы найти его разложение на сомножители на современных компьютерах. Именно на этой сложности, а вернее на том, что мы не знаем быстрого способа раскладывать большие числа на сомножители, основываются некоторые криптографические алгоритмы, например RSA.

Задача со звёздочкой

Пусть у нас есть 64-битное целое беззнаковое число с начальным значением 0, для скорости обработки хранящееся в регистре общего назначения 64-битного процессора. Будем увеличивать значение в этом регистре на единицу в каждой итерации цикла, не делая больше ничего, пока оно не достигнет максимально возможной величины (все биты равны 1). Какое время будет выполняться эта задача на современном процессоре с частотой 3 ГГц?

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

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

Я подумал, что если взять два числа из нашей таблицы остатков деления на 30, то их произведение будет иметь строго определённый остаток от деления на 30, а зная этот остаток, мы можем узнать, каким столбцам могут принадлежать исходные сомножители. Таким образом, у нас появляется дополнительная информация о исходных числах, произведение которых нам нужно факторизовать!

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

N = p \cdot q,

где N - факторизуемое число.

Hidden text

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

Т.е. если мы возьмем любые p и q из, например, первого и седьмого столбца, то их произведение N всегда будет в седьмом столбце (N \equiv 7 \pmod{30}), как и для p и q из одиннадцатого и семнадцатого столбцов. Это следует из того, что для любых
x := p \bmod 30 и y := q \bmod 30 верно, что

x \cdot y \equiv p \cdot q \pmod{30}

То есть, произведение остатков по модулю 30 (остатков от деления чисел p и q на 30) соответствует остатку от деления произведения p и q на 30.

Hidden text

Пусть

x \equiv 7 \pmod{30}, \quad y \equiv 11 \pmod{30}

Тогда:

x \cdot y \equiv 7 \cdot 11 \equiv 17 \pmod{30}

Если взять любые другие x', y', такие что:

x' = 7 + 30k, \quad y' = 11 + 30m, \quad \text{где } k, m \in \mathbb{Z}

то:

x' \cdot y' \equiv 7 \cdot 11 \equiv 17 \pmod{30}

Если вам, как и мне, более понятны словесные описания, то из всего этого следует, что мы можем выразить разность D между двумя произвольными числами p и q как сумму числа полных строк между ними в нашей таблице остатков по модулю 30 — n, умноженного на количество чисел в строке — 30, и абсолютного значения разности между номерами столбцов, в которых находятся p и q\delta. На языке математики это записывается так:

D = p - q = 30n + \delta, \quad \text{где } n \in \mathbb{Z},\ \delta \in \Delta

где

\delta \equiv |p - q| \pmod{30}

Это позволяет нам вычислить все возможные значения \delta для каждого v \equiv N \pmod{30} и занести их в таблицу дельт, которую мы будем использовать в дальнейшем.

Hidden text

Чтобы построить эту таблицу, я перебрал все возможные пары чисел из \mathcal{R}_{30}, назовем числа каждой пары: a и b, где a \le b. Для каждой пары я:

  1. Перемножал эти два числа и вычислял остаток от деления на 30

v = a \cdot b \bmod{30}
  1. Вычислял разность:

\delta=b-a
  1. Группировал полученные \delta по соответствующему значению v.

Таким образом, для каждого возможного остатка v \in [0, 29] получался список дельт, которые реально возникают при перемножении.

Дополнительно я применил два метода фильтрации:

  • Исключил значения \delta, которые никогда не приводят к полному квадрату при вычислении m^2 = D^2 + 4N (подробнее см. далее);

  • Убрал такие дельты, которые дают одинаковые значения D при вычислениях.

Это позволило сократить число возможных дельт для каждого N \pmod{30}, то есть сократить максимальное теоретическое число операций при факторизации N.

Ключевая формула метода

Для факторизации заданного числа N мы должны найти такое значение D, при котором D^2 + 4N является полным квадратом (квадратным числом):

m = \sqrt{D^2 + 4N} \in \mathbb{Z},

Если мы найдём такое целочисленное значение m, то сомножители N находятся сразу по формулам:

p = \frac{m+D}{2}, \quad q = \frac{|m-D|}{2}.

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

Как работает алгоритм?

Сначала мы должны взять из таблицы все подходящие значения \delta для v = N \bmod 30 — мы будем подставлять их в формулу (30n + \delta)^2 + 4N и проверять, не получился ли в итоге полный квадрат. Если получился, то мы можем остановить алгоритм и найти значения сомножителей p и q. Однако алгоритм является универсальным и может быть применён к составным числам, число простых сомножителей которых больше двух. Поэтому, если мы не знаем наверняка, что число N является полупростым (произведением двух простых сомножителей), мы можем продолжить поиск всех сомножителей, пока не переберем все подходящие n и \delta. При этом абслютная величина n ограничена сверху:

|n| \le \left\lfloor \frac{N}{30} \right\rfloor.

Т.е. n не может превышать наше начальное число N, разделенное на 30 с округлением вниз до ближайшего целого. Мы можем перебирать абсолютные значения n от 0 до максимально возможного — в этом случае мы сначала проверяем близкие по значению кандидаты в сомножители, постепенно увеличивая разницу между ними, или наоборот — от максимально возможного до 0, изначально проверяя максимально удалённые друг от друга кандидаты, или использовать какую-нибудь комбинированную стратегию. Следует учитывать, что n может принимать и отрицательные значения, поскольку мы не знаем заранее какое из чисел p и q является бо́льшим.

Больше об отрицательных n

Хотя операция умножения коммутативна ({p}\cdot{q}={q}\cdot{p}), данный метод основан на разности D=p−q, величина которой зависит от порядка членов. Чтобы учесть все возможные случаи, мы допускаем отрицательные значения n, что позволяет находить решение вне зависимости от того, какой из сомножителей, p или q больше.

Пример 1:

Допустим, нам нужно факторизовать: N = 13315381 = 3929 \times 3389

  1. N \bmod 30 = 1

  2. Таблица даёт: \delta \in \{0, 6\}

  3. Пробуем \delta = 0, перебираем n = 0, 1, 2, \dots

  4. При n = 18:

D = 30 \cdot 18 + 0 = 540m^2 = 540^2 + 4 \cdot 13315381 = 53553124 = 7318^2
  1. Множители:

p = \frac{7318 + 540}{2} = 3929, \\q = \frac{7318 - 540}{2} = 3389

Мы нашли простые сомножители за 19 итераций. Если бы мы использовали метод Ферма, то смогли бы обнаружить эти сомножители всего за 10 итераций. В данном случае наш метод проигрывает методу Ферма из-за того, что p и q различаются незначительно, поскольку метод Ферма наиболее эффективен именно когда сомножители числа примерно равны между собой.

Попробуем пример с большей абсолютной разницей между p и q.

Пример 2:

N=190223063

Аналогично:

  1. N \bmod 30 = 23

  2. Допустимые дельты: \{2, 22\}

  3. При n = 300 и \delta = 2:

D={30}\cdot{300}+2=9002m^2 = 9002^2 + 4 \cdot 190223063 = 841928256 = 29016^2
  1. Получаем:

p = \frac{29016 + 9002}{2} = 19009, \\q = \frac{29016 - 9002}{2} = 10007

Мы нашли сомножители за 301 итерацию. Алгоритму Ферма потребовалось бы 715 итераций для поиска сомножителей.

Hidden text

При использовании метода Ферма мы бы начали и закончили значениями:

a_0=\lceil\sqrt{190223063}\rceil=13793,\quad \\a=\frac{p+q}{2}=\frac{10007+19009}{2}=14508

Что даёт 14508 - 13793 = 715 итераций.

Пример 3: отрицательное n

N = 1936007 = 2341 \times 827

  1. N \bmod 30 = 17

  2. Допустимые дельты: \{4, 16\}

  3. При n = -51 и \delta = 16:

D={30}\cdot{-51}+16=-1514m^2 = (-1514)^2+4 \cdot 1936007 = 10036224 = 3168^2
  1. Получаем:

p = \frac{-1514 + 3168}{2} = 827, \\q = \frac{|-1514 - 3168|}{2} = 2341

Таблица допустимых дельт (фрагмент)

N \bmod 30

\delta

0

0, 1, ..., 14

1

0, 6

2

1, 4, 11, 14

23

2, 22

29

8, 10, 28

(Полную таблицу см. в препринте)

Сложность алгоритма

Если k — число допустимых дельт (обычно 2–4), то:

O\left( \frac{2k \sqrt{N}}{30} \right) = O\left( \frac{k \sqrt{N}}{15} \right),

где поправочный коэффициент 2 введён из-за того, что мы должны перебирать как положительные, так и отрицательные n.

В худшем случае (при k = 15) данный метод имеет сложность O(\sqrt{N}), что совпадает со сложностью метода Ферма. Однако, из-за того, что в среднем k \approx 3 количество необходимых проверок уменьшается в 30 / (2k) \approx 5 раз, делая данный метод существенно быстрее для сравнимых значений |p - q| \gg 0.

Заключение

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

Что мне особенно хочется отметить:

  • он не зависит от размерности N, только от разности |p - q|;

  • его можно легко распараллелить, скорость расчётов при этом растёт линейно (с поправкой на производительность узлов кластера);

  • можно использовать различные стратегии подбора n, что делает его особенно гибким.

Если вы увлекаетесь криптографией, алгоритмами или просто любите математику, а также умеете писать программы — попробуйте реализовать его сами на своем любимом ЯП. Я сделал это на C++, Python и Matlab. А если у вас под рукой есть вычислительный кластер... вдруг получится факторизовать что-то большое? Например, замахнуться на поиск решений RSA-чисел, многие из которых не найдены до сих пор. Если у вас это получится сделать с помощью метода решета дельт, обязательно дайте мне знать, мне будет очень приятно!

Полный препринт на OSF

Hidden text

Препринт опубликован на osf.io, т.к. я не являюсь математиком, не вхожу в академическую среду и у меня нет endorsement на arXiv.

? Комментарии c критикой и идеями по оптимизации — горячо приветствуются!

Комментарии (3)


  1. AndriyS
    20.05.2025 06:20

    По ссылке на препринт открывает страницу с ошибкой : DOI Not Found.


    1. d_ilyich
      20.05.2025 06:20

      Кажется, вот правильная


    1. ooptimum Автор
      20.05.2025 06:20

      Очень странно, ещё вчера всё работало. Спасибо, что заметили! Поправил ссылку в статье.