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

