Нечётные числа

Что же я там считаю такое, что мне понадобились 128-битные числа? Сейчас расскажу.

Коллега в офисе, который когда-то работал математиком в местном университете, наткнулся в тот свой период на интересную задачку в каком-то в иностранном журнале.

Рукописные математические записи на листе бумаги:  \in X$, x \in X \Rightarrow x \in X$, $x \in X \Rightarrow (2x+1) \in X$, и фраза «натуральные числа».

Формулировка у неё была очень простая.

Дано множество X. Единица принадлежит X. Если 3×x принадлежит X, то и x принадлежит X. Если x принадлежит X, то и 2×x + 1 принадлежит X. При этом x — натуральное, разумеется.

Требуется доказать, что таким образом можно получить множество всех натуральных нечётных чисел. Авторы статьи, кстати, были убеждены, что найти доказательство у читателей не получится.

Упомянутый коллега у себя в университете при помощи студента, Фортрана и Пролога пытался проверить эту теорему по следующему алгоритму.

Берём число, например, «9». Оно могло быть получено, согласно перечисленным утверждениям, двумя путями: либо какое-то другое нечётное число умножили на два и прибавили один, либо умножили на три. Поэтому надо проверить по формулам откуда мы могли попасть в это число, отсекая циклы и останавливаясь, когда мы попадаем в чётные числа.

В этой задаче очень быстро растёт разрядность, поэтому в те времена рассчитать даже первую сотню чисел было достаточно проблематично.

В общем, я решил с этой задачкой побаловаться.

Для начала написал программу, которая вычисляет эти числа перебором. Начинаем, понятное дело, с единицы, следующее число получаем по формуле 2×x + 1, потом, пока число делится на три, делим и выводим эти числа, считая для каждого ещё и  2×x + 1 и так далее. Кроме того, пришлось ещё запоминать все найденные числа, чтобы избегать циклов.

Первую версию я написал с использованием рекурсии, но программа падала примерно через полторы минуты работы — стек просто кончался. Пришлось переписать. Новая версия работает уже несколько часов на моём ноутбуке и вычислила за это время уже почти 17 миллионов чисел. Оставлю её на ночь, интересно, закончит ли она к утру.

Комментарии 33

SiMMmr E16/NR04δ13 № 1
а-ля решето Эратосфена напрашивается, не?
Евгений Степанищев автор № 3
Ну, это не оно
SiMMmr E16/NR04δ13 № 5
так в том-то и вопрос — не рациональнее было использовать алгоритм наподобие решета Эратосфена, вычёркивая в битовых полях подходящие номера бит (от нуля до n=8×N, где N — количество выделенных байт памяти для проверки верности гипотезы в диапазоне от 0 до n)
если в результате по завершению работы «решета» все байты равны 0xAA — в диапазоне 0…n гипотеза подтвердилась
Евгений Степанищев автор № 6
Так потребление памяти копеечное и это за всю ночь, а вычислительные затраты возрастают, какой смысл?
SiMMmr E16/NR04δ13 № 7
Евгений Степанищев, неочевидно 🤷‍♂️
Евгений Степанищев автор № 8
SiMMmr E16/NR04δ13, как это? нужны доп вычисления, чтобы в нужный бит адресоваться, плюс операция ИЛИ для установки.
SiMMmr E16/NR04δ13 № 9
Евгений Степанищев, не самые сложные операции по сравнению с делением и двоичным поиском
Евгений Степанищев автор № 10
SiMMmr E16/NR04δ13, Пожалуй… У меня была мысль хранить в битах, но я попытался прикинуть сколько их надо и откинул её. Сейчас уже не помню весь ход рассуждений.
Евгений Степанищев автор № 11
SiMMmr E16/NR04δ13, Вроде вместо экономии памяти там был её дикий расход, но я правда не помню пока ход рассуждений, я проснулся недавно.
Владимир Новицкий № 2
…получить множество всех натуральных нечётных чисел.
Новая версия работает уже несколько часов на моём ноутбуке и вычислила за это время уже почти 17 миллионов чисел.

Я не силен в математике, а сколько их должно быть?

Евгений Степанищев автор № 4
У самого типа счёт на ундециллионы, но сколько чисел оно нагенерит — непонятно.
Evgeny Sureev № 12
Ну так то всех нечетных чисел бесконечное количество.
Евгений Степанищев автор № 13
Так понятно, что надо искать аналитическое доказательство. Эта работа была, видимо, чтобы какие-то закономерности уловить, но это моё предположение.
Роман № 14
Давно я не брал в руки С, поправьте, если я не прав. Но меня смущают постоянный memmove(). Как бы оно половину производительности не съело.
Евгений Степанищев автор № 15
Это для вставки, чтобы потом делать бинарный поиск, а не линейный. Чтений ожидается больше вставок.
Роман № 16
Да, но если числа хранить сразу в виде дерева, то поиск останется O(logN), а вот вставка будет быстрее. Но это конечно сложнее реализовать, возможно данный эксперимент того не стоит
Роман № 20
Все-таки вставка будет O(logN) https://en.wikipedia.org/wiki/AVL_tree
Но с точки зрения программирования это конечно головная боль
Евгений Степанищев автор № 21
Ну, там балансировка, тут — копирование элементов. Если я всё правильно понимаю, порядок операций тот же самый. Сколько элементов надо перебалансировать в случае дерева, столько надо скопировать и тут, разве нет?
Leonid № 22
Я всё таки не понимаю , что проверяет программа? Что в какой то момент будет найдено нечётное число которое нельзя получить этим алгоритмом?
Евгений Степанищев автор № 23
Программа проверяет до какого числа и за какое время можно дойти современным железом. По сути, больше ничего. Ну и с такими большими числами хотелось в Си поработать, как узнаешь их ограничения, если не попробуешь?
Роман № 24
Зачем бы тогда вообще придумывали деревья, если бы они не были быстрее? Бинарный поиск можно и в массиве делать. А вот сложность вставки в массив (в худшем случае) — это O(N). Это куда менее быстро, чем гарантированные O(logN) в AVL-дереве.
Евгений Степанищев автор № 25
Да, точно, был неправ )

Думаю, я всё-таки остановлю расчёт цифр, слишком уж долго. Не знаю где там конец, но меня печалит столько времени ноут на зарядке держать.

Можно, кстати, lock-free дерево сделать вообще и на потоки перейти. Где-то я видел библиотеку с готовой реализацией.
Роман № 26
Да, многопоточность тут может сильно помочь по идее. Ядер у нынешних процессоров много.
Евгений Степанищев автор № 27

У моего ноута — 14, но скорее всего он нагреется ещё больше и будет троттлить. У него уже сейчас температура 60°.

У нас на работе есть компьютер с водяным охлаждением. Можно бы его часов на 40 зарядить для начала — посмотреть будет ли быстрее на одном ядре. А потом, если будет, попробовать сделать всё в десять потоков.

Мистер Икс № 28
Откуда такое мнение?

Ну, там же наверняка число какое-нибудь уровня задачки про зерна и шахматы. Если это 128-битное число без знака, то оно может быть в диапазоне от 0 до 340 282 366 920 938 463 463 374 607 431 768 211 455. Очевидно, что перебор такого диапазона на ноутбуке, с учетом того, что каждый разряд умножает длительность в десять, может занимать непредсказуемое время — дни или недели как минимум.

Евгений Степанищев автор № 29
Я надеялся, что при восхождении (умножении) ветвление будет быстро упираться в потолок и обрубать эти ветки.
Oleg Oriscthenko № 32
Дополнительные материалы:
https://habr.com/ru/articles/870220/
Гипотеза Коллатца как фейл мировой математики / Хабр
Евгений Степанищев автор № 33
Спасибо!

Я пока сделал паузу — у меня перебираются в один поток 64-битные числа, а многопоточную версию я пока не дописал. Закоммитил промежуточное состояние, но там есть ошибки.