Недавно я был совершенно сбит с толку этим твитом «Библиотеки Ферма»:

«Вот что получится, если в факториале не умножать, а делить.»
Когда я увидел его, мне пришлось бросить свои дела, схватить блокнот и проверить формулу. Результат в черновом виде казался логичным. Так как мультипликативная версия
при увеличении
стремится к бесконечности, то «делительная» версия должна стремиться к нулю. И
ведёт себя именно так; полиномиальная функция
растёт медленнее, чем степенная функция
для достаточно больших
:
Но почему результат деления принимает именно вид
? Откуда берётся
?
Чтобы ответить на этот вопрос, мне пришлось разбередить старую травму, связанную с изучением деления дробей, но я справился с болью. Двигаясь по формуле из твита слева направо, мы сначала получаем
. Затем, поделив эту величину на
, получаем
Продолжая таким образом, мы в результате приходим к:
Чтобы прийти к показанному в твите результату
, мы просто умножим числитель и знаменатель на
. (Хотя на мой вкус, выражение
более понятно.)
Я официально признанный фанат факториалов. Оставьте при себе свои последовательности Фибоначчи; вот моя любимая функция. Каждый раз, когда я изучаю новый язык программирования, моим первым упражнением становится написание нескольких процедур для вычисления факториалов. За многие годы я придумал несколько вариаций этой темы, например, замену в определении
на
(что даёт нам треугольные числа). Но кажется, что раньше я никогда не задумывался о замене
на
. Получается странно. Так как умножение коммутативно и ассоциативно, мы можем определить
просто как произведение всех целых чисел от
до
, не беспокоясь о порядке операций. Но при делении порядок игнорировать не получится. В общем случае,
и
.
В твите «Библиотеки Ферма» делители поставлены в порядке по убыванию:
. Очевиднее всего будет заменить это на порядок по возрастанию:
. Что произойдёт, если мы зададим факториал деления как
? Ещё один возврат к школьному алгоритму деления дробей даёт нам простой ответ:
Другими словами, когда мы многократно выполняем деление, выполняя подсчёт от
до
, окончательный результат будет равен величине, обратной
. (Мне хотелось бы поставить в конце этого предложения восклицательный знак, но увы!) Если вы ищете канонический ответ на вопрос «Что мы получим при делении вместо умножения в
?», то я бы заявил, что
— лучший кандидат, чем
. Почему бы нам не принять симметрию между
и обратной ему величиной?
Разумеется, есть множество других способов размещения n целочисленных значений во множестве
. Но сколько именно? Как оказалось, ровно
! Поэтому, может показаться, что есть
уникальных способов задания делительной функции
. Однако, изучение ответов двух показанных выше перестановок даёт нам понять, что здесь работает более простой паттерн. Какой бы элемент последовательности не появился первым, он оказывается в числителе большой дроби, а знаменателем оказывается произведение всех других элементов. Поэтому в итоге остаётся всего
различных результатов (если предположить, что мы всегда выполняем операции деления строго слева направо). Для любого целочисленного
в интервале от
до
, поставив
в начало очереди, мы создаём делительное
, равное
, поделённому на все другие коэффициенты. Можно записать это следующим образом:
И таким образом мы решили небольшую загадку о том, как в этом твите
превратилось в
.
Стоит заметить, что все эти функции сходятся к нулю при стремлении
к бесконечности. С асимптотической точки зрения,
идентичны.
Та-да! Миссия выполнена. Задача решена. Дело сделано. Теперь мы знаем всё, что нам нужно, о делительных факториалах, верно?
Ну, возможно, есть ещё один вопрос. Что скажет компьютер? Если взять наш любимый алгоритм факториала, и сделать то, что предлагается в твите, заменив все вхождения оператора
(или
вариантов делительного
выдаст нам программа?
Вот мой любимый алгоритм для вычисления факториалов в виде программы на Julia:
Этот алгоритм познакомил целые поколения нердов с концепцией рекурсии. В текстовом виде он гласит: если
равно
, то
равно
. В противном случае нужно вычислить функцию
, а затем умножить результат на
.
Вы можете спросить, что произойдёт, если
будет равным нулю или отрицательным. Спросить вы можете, но лучше не надо. Для наших текущих целей
.
Начав с любого положительного
, последовательность рекурсивных вызовов рано или поздно опустится к
.
Функцию можно записать более лаконично с помощью однострочного стиля определений Julia:
Правая часть оператора присваивания — это условное выражение, или тернарный оператор, имеющий вид
Просто чтобы убедиться, что я сделал всё верно, вот первые 10 факториалов, вычисленных этой программой:
Теперь давайте изменим это определение и преобразуем единственное вхождение
И вот что вернёт программа, если мы запустим её для значений
от
до
:
Что? Это точно не походит на схождение к нулю, как и на
или
. На самом деле значения так не выглядят, потому что и не собираются сходиться. Судя по показанному ниже графику, последовательность состоит из двух перемежающихся компонентов, каждый из которых, похоже, медленно растёт в сторону бесконечности, а также отклоняется от другого.

Разбираясь с тем, что же мы здесь наблюдаем, полезно будет изменить тип выходных данных функции
Вот последовательность значений для
В списке полно любопытных паттернов. Это двойная спираль, в которой чётные и нечётные числа зигзагами перемещаются в комплементарных нитях. Чётные числа не просто чётные, все они являются степенями
. Кроме того, они появляются в парах — сначала в числителе, затем в знаменателе — и их последовательность неубывающая. Но существуют пробелы; присутствуют не все степени
. Нечётная нить выглядит ещё более сложной, в числах появляются и исчезают разные небольшие простые коэффициенты. (Простые числа должны быть малыми, как минимум, меньше
.)
Этот результат удивил меня. Я ожидал увидеть гораздо более смирную последовательность, наподобие тех, которые я вычислял на бумаге. Все эти изломанные скачки вверх и вниз не имели никакого смысла. Как не имел смысла и общий тренд к неограниченному росту соотношения. Как мы можем постоянно делить, получая при этом всё бОльшие и бОльшие числа?
На этом этапе можете приостановить чтение и попытаться придумать собственную теорию о том, откуда взялись эти зигзагообразные числа. Если вам нужна подсказка, то у вас она есть, и очень сильная, почти спойлер: поищите последовательность числителей или последовательность знаменателей в «Онлайн-энциклопедии целочисленных последовательностей».
Вот ещё одна подсказка. Небольшое изменение в программе
Теперь результаты выглядят вот так:
Это обратная функция факториала, которую мы уже видели, ряд значений, сгенерированные при обходе слева направо по возрастающей последовательности делителей
.
Неудивительно, что изменение последнего выражения в процедуре менят результат. В конце концов, мы знаем, что деление не коммутативно и не ассоциативно. Но сложно понять, почему последовательность сгенерированных исходной программой значений даёт такую странную зигзагообразную форму. Какой механизм порождает такие парные степени двойки и попеременные нечётные и чётные значения?
Я обнаружил, что объяснить происходящее в зигзагообразной последовательности проще на итеративной версии процедуры, а не на рекурсивной. (Это заявление может показаться досадным тем, кто считает рекурсивные определения более простыми, но так уж получилось.) Вот как выглядит программа:
Я заявляю, что эта процедура с циклом по функционалу идентична рекурсивной функции, в том смысле, что если
Чтобы понять процесс, порождающий эти числа, рассмотрим последовательные значения переменных
и
при каждом выполнении цикла. Изначально
и
равны
; поэтому после первого прохода цикла выражение
значение
. Затем
, а
, то есть новое значение
равно
. При третьей итерации
, а
, что даёт нам
. Если это всё ещё сбивает вас с толку, то представьте
как
. Важным наблюдением здесь является то, что при каждом обходе цикла
получает обратное значение, становясь
.
Если развернуть эти операции и посмотреть на умножения и деления, входящие в каждый элемент ряда, то возникает паттерн:
В общем виде:
Функции
для нечётного
и
для чётного
имеют собственное название! Они называются двойными факториалами, и записываются как
.
Ужасная терминология, правда? Лучше бы их назвали «полуфакториалами». И если бы я этого не знал, то прочитал бы
как «факториал факториала».
Двойной факториал n определяется как произведение n и всех меньших положительных целых чисел той же чётности. Таким образом, наша любопытная последовательность зигзагообразных значений — это просто
.
В статье 2012 года Генри У. Гулда и Джослин Куэнтенс (увы, находящаяся за paywall) исследуются применения двойных факториалов. Они встречаются гораздо чаще, чем можно подумать. В середине 17-го века Джон Валлис вывел следующее тождество:
Ещё более странный ряд с участием куба значений двойных факториалов суммируется в
. Его обнаружил не кто иной, как Сриниваса Рамануджан.
Гулд и Киэнтенс также рассматривали эквивалент двойного факториала для биномиальных коэффициентов. Стандартный биномиальный коэффициент определяется как:
Двойная версия выглядит так:
Заметьте, что наши зигзагообразные числа соответствуют этому описанию, а потому могут считаться биномиальными коэффициентами двойных факториалов. Говоря конкретнее, они являются такими числами:
Обычный бином
не очень интересен; он просто равен
. Но двойная версия
, как мы видели, танцует более оживлённый танец. И в отличие от обычного бинома она не всегда бывает целочисленной. (Единственные целые значения — это
и
.)
Взгляд на зигзагообразные числа как на частное двойных факториалов объясняет довольно многие их свойства, начиная с попеременных чётных и нечётных значений. Также мы можем увидеть, почему все чётные числа в последовательности являются степенями 2. Рассмотрим пример с
. Числитель этой дроби — это
, получающий от
множитель
. Но знаменатель равен
. Тройки сверху и снизу сокращаются, оставляя нам
. Такие сокращения происходят в каждом из случаев. Каждый раз, когда в последовательности чётных чисел появляется нечётный множитель
, он обязательно имеет вид
, но к этому времени само
уже должно появиться в последовательности нечётных чисел.
Является ли последовательность зигзагообразных чисел разумным ответом на вопрос: «Что произойдёт, если мы будем делить, а не умножать в
?» Или генерирующая их компьютерная программа просто оказалась ошибочным алгоритмом? По моему личному мнению,
— более интуитивный ответ, зато
— более интересный.
Более того, само существование зигзагообразной последовательности расширяет наши горизонты. Как сказано выше, если вы настаиваете, что алгоритм деления всегда должен по порядку проходить по списку числителей
, на каждом шаге деля число слева на число справа, то имеется всего
возможных результатов, и все они выглядят очень похожими. Но зигзагообразное решение обеспечивает намного более широкие возможности. Мы можем сформулировать задачу следующим образом: возьмём множество числителей
, выберем его подмножество и обратим все элементы этого подмножества; теперь перемножим все числители, как обратные, так и прямые. Если обращённое подмножество пусто, то результатом будет обычный факториал
. Если все числители превратились в обратные им значения, то мы получаем обратный
. А если обращён каждый второй числитель, начиная с
, то результатом будет элемент зигзагообразной последовательности.
Это только некоторые из множества возможных вариантов; в сумме здесь есть
подмножеств из
элементов. Например, можно брать обратные значения каждого числа, являющегося простым или степенью простого числа
. При малых
результаты скачут, но постоянно остаются меньше, чем
:

Однако если бы я продолжил этот график для бОльших
, он бы взлетел в стратосферу. Степени простых чисел становятся очень разреженными на числовой прямой.
Теперь я задам вопрос. Мы видели вариации факториалов, приближающиеся к нулю при стремлении
к бесконечности, например
. Мы видели другие вариации, растущие при увеличении
безгранично, в том числе сам
и зигзагообразные числа. Существуют ли какие-то разновидности процесса факториалов, сходящиеся к какой-то конечной границе, не являющейся нулём?
В первую очередь мне пришёл в голову такой алгоритм:
Мы циклически перебираем целые значения от
вниз до
, вычисляя в процессе текущее произведение/частное
. На каждом шаге, если текущее значение
больше
, мы делим его на следующий числитель, в противном случае, выполняем умножение. Эта схема реализует своего рода управление обратной связью или поведение поиска цели. Если
становится слишком большим, мы уменьшаем его; если слишком маленьким, мы увеличиваем его. Я предположил, что при стремлении
к бесконечности,
будет сходиться к постоянно сужающемуся интервалу значений рядом с
.
Но эксперимент подкинул мне ещё один сюрприз:

Такая пилообразная волна — не совсем то, чего я ожидал. Любопытно, что кривая не симметрична около
; отклонения сверху имеют бОльшую амплитуду, чем снизу. Но это искажение больше визуальное, чем математическое. Так как
является частным, расстояние от
до
такое же, как расстояние от
до
, но в линейном масштабе таким не выглядит. Исправить это можно, составив логарифмический график частного:

Теперь график симметричен, или хотя бы приблизительно таков, и центрирован относительно значения
, которое является логарифмом
. Но остаётся более серьёзная тайна. Пилообразная волна очень регулярна и имеет период
, при этом не показывает признаков сжатия по направлению к ожидаемому ограничивающему значению
. Численные значения предполагают, что при стремлении
к бесконечности пики кривой сходятся к значению чуть выше
, а минимумы приближаются к значению чуть ниже
. (Соответствующие логарифмы по основанию
примерно равны
). Мне не удалось разобраться, почему так происходит. Возможно, кто-то сможем объяснить.
Неудача с этим жадным алгоритмом не означает, что мы не сможем делительный факториал, сходящийся к
.
Если мы будем работать с логарифмами числителей, то эта процедура становится случаем хорошо известной вычислительной задачи под названием «задача разбиения множества чисел». Нам даётся множество вещественных чисел, и мы должны разделить их на два множества, сумма которых равна, или как можно ближе к равенству. Это подтверждённо сложная задача, но её также называют (PDF) «простейшей сложной задачей».
Для любого
мы можем обнаружить, что при использовании обратных значений какого-то другого подмножества числителей даёт нам лучшее приближение к
. Для малых
мы можем решить эту задачу способом перебора: просто рассмотреть все
подмножеств и выбрать самое лучшее.
Я вычислил оптимальные разбиения вплоть до
, когда выбирать нужно из миллиарда вариантов.

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

«Вот что получится, если в факториале не умножать, а делить.»
Когда я увидел его, мне пришлось бросить свои дела, схватить блокнот и проверить формулу. Результат в черновом виде казался логичным. Так как мультипликативная версия
Но почему результат деления принимает именно вид
Чтобы ответить на этот вопрос, мне пришлось разбередить старую травму, связанную с изучением деления дробей, но я справился с болью. Двигаясь по формуле из твита слева направо, мы сначала получаем
Продолжая таким образом, мы в результате приходим к:
Чтобы прийти к показанному в твите результату
Я официально признанный фанат факториалов. Оставьте при себе свои последовательности Фибоначчи; вот моя любимая функция. Каждый раз, когда я изучаю новый язык программирования, моим первым упражнением становится написание нескольких процедур для вычисления факториалов. За многие годы я придумал несколько вариаций этой темы, например, замену в определении
В твите «Библиотеки Ферма» делители поставлены в порядке по убыванию:
Другими словами, когда мы многократно выполняем деление, выполняя подсчёт от
Разумеется, есть множество других способов размещения n целочисленных значений во множестве
И таким образом мы решили небольшую загадку о том, как в этом твите
Стоит заметить, что все эти функции сходятся к нулю при стремлении
Та-да! Миссия выполнена. Задача решена. Дело сделано. Теперь мы знаем всё, что нам нужно, о делительных факториалах, верно?
Ну, возможно, есть ещё один вопрос. Что скажет компьютер? Если взять наш любимый алгоритм факториала, и сделать то, что предлагается в твите, заменив все вхождения оператора
*
) на /
, то что случится? Какие из Вот мой любимый алгоритм для вычисления факториалов в виде программы на Julia:
function mul!(n)
if n == 1
return 1
else
return n * mul!(n - 1)
end
end
Этот алгоритм познакомил целые поколения нердов с концепцией рекурсии. В текстовом виде он гласит: если
Вы можете спросить, что произойдёт, если
Начав с любого положительного
Функцию можно записать более лаконично с помощью однострочного стиля определений Julia:
mul!(n) = n == 1 ? 1 : n * mul!(n - 1)
Правая часть оператора присваивания — это условное выражение, или тернарный оператор, имеющий вид
a ? b : c
. Здесь a
— булево условие теста, которое должно вернуть значение или true
, или false
. Если a
равно true
, то вычисляется выражение b
, а результат становится значением всего выражения. В противном случае вычисляется c
.Просто чтобы убедиться, что я сделал всё верно, вот первые 10 факториалов, вычисленных этой программой:
[mul!(n) for n in 1:10]
10-element Array{Int64,1}:
1
2
6
24
120
720
5040
40320
362880
3628800
Теперь давайте изменим это определение и преобразуем единственное вхождение
*
в /
, оставив всё остальное неизменным (за исключением названия функции).div!(n) = n == 1 ? 1 : n / div!(n - 1)
И вот что вернёт программа, если мы запустим её для значений
[div!(n) for n in 1:20]
20-element Array{Real,1}:
1
2.0
1.5
2.6666666666666665
1.875
3.2
2.1875
3.657142857142857
2.4609375
4.063492063492063
2.70703125
4.432900432900433
2.9326171875
4.773892773892774
3.14208984375
5.092152292152292
3.338470458984375
5.391690662278897
3.523941040039063
5.675463855030418
Что? Это точно не походит на схождение к нулю, как и на
Разбираясь с тем, что же мы здесь наблюдаем, полезно будет изменить тип выходных данных функции
div!
. Вместо использования оператора деления /
, который возвращает значение как число с плавающей запятой, мы можем заменить его оператором //
, возвращающим точное рациональное значение, округлённое до младшего члена.div!(n) = n == 1 ? 1 : n // div!(n - 1)
Вот последовательность значений для
n в интервале 1:20
:20-element Array{Real,1}:
1
2//1
3//2
8//3
15//8
16//5
35//16
128//35
315//128
256//63
693//256
1024//231
3003//1024
2048//429
6435//2048
32768//6435
109395//32768
65536//12155
230945//65536
262144//46189
В списке полно любопытных паттернов. Это двойная спираль, в которой чётные и нечётные числа зигзагами перемещаются в комплементарных нитях. Чётные числа не просто чётные, все они являются степенями
Этот результат удивил меня. Я ожидал увидеть гораздо более смирную последовательность, наподобие тех, которые я вычислял на бумаге. Все эти изломанные скачки вверх и вниз не имели никакого смысла. Как не имел смысла и общий тренд к неограниченному росту соотношения. Как мы можем постоянно делить, получая при этом всё бОльшие и бОльшие числа?
На этом этапе можете приостановить чтение и попытаться придумать собственную теорию о том, откуда взялись эти зигзагообразные числа. Если вам нужна подсказка, то у вас она есть, и очень сильная, почти спойлер: поищите последовательность числителей или последовательность знаменателей в «Онлайн-энциклопедии целочисленных последовательностей».
Вот ещё одна подсказка. Небольшое изменение в программе
div!
полностью преобразует выходные данные. Просто изменим последнее выражение, заменив n // div!(n - 1)
на div!(n - 1) // n
.div!(n) = n == 1 ? 1 : div!(n - 1) // n
Теперь результаты выглядят вот так:
10-element Array{Real,1}:
1
1//2
1//6
1//24
1//120
1//720
1//5040
1//40320
1//362880
1//3628800
Это обратная функция факториала, которую мы уже видели, ряд значений, сгенерированные при обходе слева направо по возрастающей последовательности делителей
Неудивительно, что изменение последнего выражения в процедуре менят результат. В конце концов, мы знаем, что деление не коммутативно и не ассоциативно. Но сложно понять, почему последовательность сгенерированных исходной программой значений даёт такую странную зигзагообразную форму. Какой механизм порождает такие парные степени двойки и попеременные нечётные и чётные значения?
Я обнаружил, что объяснить происходящее в зигзагообразной последовательности проще на итеративной версии процедуры, а не на рекурсивной. (Это заявление может показаться досадным тем, кто считает рекурсивные определения более простыми, но так уж получилось.) Вот как выглядит программа:
function div!_iter(n)
q = 1
for i in 1:n
q = i // q
end
return q
end
Я заявляю, что эта процедура с циклом по функционалу идентична рекурсивной функции, в том смысле, что если
div!(n)
и div!_iter(n)
возвращают результат для какого-то положительного целого n
, то он всегда будет одинаковым. Вот моё доказательство:[div!(n) for n in 1:20] [div!_iter(n) for n in 1:20]
1 1//1
2//1 2//1
3//2 3//2
8//3 8//3
15//8 15//8
16//5 16//5
35//16 35//16
128//35 128//35
315//128 315//128
256//63 256//63
693//256 693//256
1024//231 1024//231
3003//1024 3003//1024
2048//429 2048//429
6435//2048 6435//2048
32768//6435 32768//6435
109395//32768 109395//32768
65536//12155 65536//12155
230945//65536 230945//65536
262144//46189 262144//46189
Чтобы понять процесс, порождающий эти числа, рассмотрим последовательные значения переменных
q = i // q
даёт Если развернуть эти операции и посмотреть на умножения и деления, входящие в каждый элемент ряда, то возникает паттерн:
В общем виде:
Функции
Ужасная терминология, правда? Лучше бы их назвали «полуфакториалами». И если бы я этого не знал, то прочитал бы
Двойной факториал n определяется как произведение n и всех меньших положительных целых чисел той же чётности. Таким образом, наша любопытная последовательность зигзагообразных значений — это просто
В статье 2012 года Генри У. Гулда и Джослин Куэнтенс (увы, находящаяся за paywall) исследуются применения двойных факториалов. Они встречаются гораздо чаще, чем можно подумать. В середине 17-го века Джон Валлис вывел следующее тождество:
Ещё более странный ряд с участием куба значений двойных факториалов суммируется в
Гулд и Киэнтенс также рассматривали эквивалент двойного факториала для биномиальных коэффициентов. Стандартный биномиальный коэффициент определяется как:
Двойная версия выглядит так:
Заметьте, что наши зигзагообразные числа соответствуют этому описанию, а потому могут считаться биномиальными коэффициентами двойных факториалов. Говоря конкретнее, они являются такими числами:
Обычный бином
Взгляд на зигзагообразные числа как на частное двойных факториалов объясняет довольно многие их свойства, начиная с попеременных чётных и нечётных значений. Также мы можем увидеть, почему все чётные числа в последовательности являются степенями 2. Рассмотрим пример с
Является ли последовательность зигзагообразных чисел разумным ответом на вопрос: «Что произойдёт, если мы будем делить, а не умножать в
Более того, само существование зигзагообразной последовательности расширяет наши горизонты. Как сказано выше, если вы настаиваете, что алгоритм деления всегда должен по порядку проходить по списку числителей
Это только некоторые из множества возможных вариантов; в сумме здесь есть
Однако если бы я продолжил этот график для бОльших
Теперь я задам вопрос. Мы видели вариации факториалов, приближающиеся к нулю при стремлении
В первую очередь мне пришёл в голову такой алгоритм:
function greedy_balance(n)
q = 1
while n > 0
q = q > 1 ? q /= n : q *= n
n -= 1
end
return q
end
Мы циклически перебираем целые значения от
Но эксперимент подкинул мне ещё один сюрприз:
Такая пилообразная волна — не совсем то, чего я ожидал. Любопытно, что кривая не симметрична около
Теперь график симметричен, или хотя бы приблизительно таков, и центрирован относительно значения
Неудача с этим жадным алгоритмом не означает, что мы не сможем делительный факториал, сходящийся к
Если мы будем работать с логарифмами числителей, то эта процедура становится случаем хорошо известной вычислительной задачи под названием «задача разбиения множества чисел». Нам даётся множество вещественных чисел, и мы должны разделить их на два множества, сумма которых равна, или как можно ближе к равенству. Это подтверждённо сложная задача, но её также называют (PDF) «простейшей сложной задачей».
Для любого
Я вычислил оптимальные разбиения вплоть до
Очевидно, что график становится всё более плоским. Можно использовать тот же метод для принудительного схождения к любому другому значению в интервале от
И таким образом мы получаем ещё один ответ на вопрос, заданный твитом и начавший наше путешествие. Что произойдёт, если мы будем делить, а не умножать в