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

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