Предположим, что у нас есть какое-то логическое утверждение, зависящее от натурального числа . Это утверждение, предположим, можно легко проверить для любого конкретного числа, но не понятно верно ли оно для любого числа.
Например, можно рассмотреть гипотезу Гольдбаха о том, что любое чётное число может быть представлено в виде суммы двух простых чисел (так, например, или ). Для любого конкретного чётного числа можно довольно легко перебором подобрать его разложение на сумму двух простых чисел. По крайней мере всегда у людей это получалось. А вот возможно ли это сделать в общем случае, то если получится ли представить в виде суммы двух простых вообще любое чётное число — вопрос, на который наука пока не нашла ответа.
Или вот можно рассмотреть последовательности Коллатца. Начальный элемент последовательности — это произвольное натуральное число, которое мы обозначим как . получается из по следующему правилу: если чётное, то . Если нечётное, то . Например, если за принять число 15, то мы получаем следующую последовательность:
Как видим, последовательность пришла в единицу. До сих пор с какого бы числа люди не начинали писать последовательность Коллатца, она всегда приходит в единицу, но приходит ли она в единицу всегда, или всё же есть исключения — никто не знает, это открытый вопрос математики.
Интуитивно должно казаться ясным, что чтобы ответить на такие вопросы, не достаточно перебрать просто большое количество вариантов. В обоих упомянутых проблемах математики перебрали уйму чисел (фактически все числа, на которые хватило вычислительных возможностей), но примера, чтобы какое-то число не раскладывалось в сумму двух простых или чтобы последовательность Коллатца для него не приходила в единицу, так и не нашли. Доказывает ли это что-либо? Конечно нет, потому что чисел, которых математики не проверили, куда больше — их ещё целая бесконечность, а проверена лишь малая конечная часть.
Так вот. Примерно 50 лет тому назад было доказано, что на самом деле для почти любого утверждения достаточно проверить лишь конечное число случаев. То есть если вплоть до некоторого номера нам не встретятся чётные числа, не представимые в виде суммы двух простых, или не встретится последовательность Коллатца, не приходящая в единицу, то такие числа нам не встретятся вовсе никогда.
Теорема. Для установления истинности почти любого утверждения достаточно проверить лишь истинность конечного числа утверждений для от 1 до некоторого .
Доказательство. Давайте представим себя на минуточку программистами. Мы хотим найти такое число , что окажется неверным, то есть мы ищем контрпример к нашей гипотезе.
Будем считать, что мы можем написать программу для проверки утверждения для любого конкретного номера (это предположение и составляет значение слова «почти» в формулировке теоремы). Напишем программу, которая будем перебирать все числа подряд начиная единицей и которая остановится лишь когда она найдёт такое , что не выполняется. Возможно, что мы не найдём его никогда и тогда программа будет работать бесконечно долго, а это значит, что у нас есть время попить чай и поразмышлять.
Код нашей программы имеет конечную длину, обозначим её N. Если рассмотреть все возможные программы длины , то среди них найдутся конечно же те, которые будут работать бесконечно долго (можно сказать, что они «зависнут») и те, которые в итоге остановятся. Обозначим за самое долгое время работы программы длины (на английском эти числа называются Busy Beaver Numbers по фольклорным причинам, а как это адекватно переводится на русский я и не знаю). Как вычислить это самое не очень понятно, но такое число вполне определено. Предположим пока, что мы его знаем.
Посмотрим на часы, сколько уже работает наша программа по поиску контрпримера, пока мы пили чай? Что? Уже дольше, чем ? Но ведь это самое долгое время, которое может работать программа, которая останавливастся. Значит мы знаем, что программа не остановится уже никогда, то есть контрпример не будет найден. Утверждение об истинности можно считать доказанным, программу можно выключать.
Конечно же, наша программа за конечное время успела проверить лишь конечное число вариантов от 1 до некоторого , но из рассуждений, приведенных выше, следует, что нам этого каким-то образом оказалось достаточно для того, чтобы утверждать об истинности для совсем любого . В чём тут подвох? Подвоха тут нет.
Задумайтесь на секундочку: почти любой сложный вопрос математики, касающийся натуральных чисел, может быть установлен путём перебора конечного числа вариантов. Будь то теорема Ферма, гипотеза Гольдбаха, Коллатца или кого ещё: достаточно проверить конечное число случаев. Не знаю как вам, но на меня в своё время этот факт произвёл неизгладимое впечатление.
Насколько этот результат практичен? На самом деле совершенно не практичен. Во-первых, не понятно как вычислить значение . Во-вторых, даже если мы его вычислим для какого-то , то оно наверняка окажется настолько гигантским, что вряд ли поместится в наш калькулятор. Так что выгоды мы из этого всего извлечь судя по всему не сможем, увы. Но сам факт!
Логично было бы, если бы после изложения этого результата я тут же показал бы, как считать числа , однако сейчас мы уйдём в сторону и я покажу как эти числа ещё можно было бы использовать, если бы они были в нашем распоряжении.
Попробуем написать такую программу, которая будет анализировать другую программу, и говорить нам о том, зависнет она или же нормально остановится. Можно рассматривать такую программу как функцию , где — множество всех возможных программ. 0 означает, что программа зависнет, 1 что остановится.
При условии, что мы можем написать программу, вычисляющую , мы нашу задачу можем очень легко решить. Пусть нам дана программа длины , для которой надо установить остановится ли она. Поскольку программа — это просто описание каких-то действий, мы можем заставить выполнять эти действия не компьютер, а нашу программу , то есть мы как бы будем симулировать выполнение программы внутри программы . Это не сложно технически, но я не буду углубляться в детали как это реализуется. Если вы знакомы с компьютерными технологиями, то наверняка слышали про интерпретаторы, виртуализацию, виртуальные машины и подобное. Это именно то, это возможно и это не особо сложно.
По ходу выполнения программы , программа будет считать, как долго работает. Если вдруг окажется, что программа работает дольше, чем время , то мы знаем, что она не закончится, и останавливает , выдавая нам результат 0. Если же остановится сама раньше, то даст результат 1. Вроде всё.
А теперь давайте напишем такую программу , которая так же будет принимать на вход какую-то другую программу , затем будет запускать на ней программу , и если , то будет зависать (например, начнёт считать все натуральные числа подряд), а если же , то она будет возвращать какой-то результат. Такую программу так же легко написать.
Попробуем понять какой результат мы получим, если на вход программе мы дадим её же саму, то есть попробуем выполнить . Что будет? Предположим, что , тогда программа должна завершиться, вернув результат. Но это противоречит тому, что . Из этого противоречия видно, что . Пусть теперь , но тогда должна зависнуть, что противоречит тому, что , а значит . Как так? оказывается не равно ни одному своему возможному значению!
Это противоречие означает, что программа не может быть написана, не существует такой программы и существовать не может. Но как так, ведь мы же чётко указали, как её можно написать! Если вглядеться, то при описании работы программы мы опирались на то, что мы можем как-то вычислить числа , но мы не показывали этого. И это единственный момент во всём рассуждении, который мы никак не обосновали. Значит, числа вычислить невозможно.
Тот факт, что есть такая функция , которая вполне определена, но которую невозможно вычислить, может показаться невероятным. На самом деле если подумать, то это довольно не сложно. Из определения функции мы могли бы попробовать вычислить , запустив одновременно все возможные программы длины , и, заведя таймер, пытаясь понять, какое самое большое время работы программы. Однако с учетом того, что есть программы, которые зависают, мы никогда не узнаем, когда таймер пора выключать, хотя конечно же такой момент во времени существует. Это и есть пример того, что функцию невозможно вычислить.
Мы получили два результата.
Теорема. Невозможно написать программу, которая бы могла по коду другой программы определить остановится ли она или зависнет.
Теорема. Числа невычислимы. Другими словами невозможно определить максимальное время, которое может работать независающая программа длины .
Приведённые рассуждения можно вывернуть ещё и вот под каким углом. Напишем программу , которая будет проверять истинность произвольного утверждения путём последовательного перебора всех возможных доказательств (программа может начинать с коротких доказательств и постепенно переходить к более длинным, так что такая программа определённо может быть написана). Если предположить, что всё можно доказать или опровергнуть, то через какое-то время наша программа либо найдёт доказательство для , либо доказательство для .
Используя эту программу, однако, мы опять же легко можем написать пресловутую программу проверяющую, остановится ли программа или нет. Просто вместо запуска программы и ожидания времени мы на этот раз будем искать доказательство остановки или зависания . Поскольку мы уже знаем, что программа не может существовать, наше предположение, что программа всегда либо найдёт доказательство либо так же не верно. Это значит, что существуют такие утверждения , которые невозможно ни доказать ни опровергнуть. Как вы вероятно помните, это есть ни что иное как теорема Гёделя о неполноте:
Теорема. Существуют утверждения, которые невозможно ни доказать ни опровергнуть.
Я упомянул, что числа окажутся скорее всего довольно большими. Оказывается, мы можем примерно прикинуть насколько именно большими они будут.
Теорема. Числа с ростом растут быстрее, чем любая последовательность, которую мы могли бы вычислить.
Доказательство. Предположим, что это не так, и что существует некоторая последовательность такая, что мы можем её вычислить и что . Но тогда мы можем запустить все программы длины и останавливать их как только они отработают время . Среди программ, остановившихся раньше, выберем ту, что работала дольше. Время, которое она работала — это и есть . Но это значит, что мы смогли их вычислить, а это противоречит теореме 3.51. Значит, такой последовательности всё же не существует.
Упражнение. Если предположить, что мы имеем в нашем распоряжения числа и неограниченный вычислительный ресурс, проверка истинности произвольных утверждений может всё равно вызывать трудности. Гипотеза Коллатца является как раз таким примером. Если наша программа поиска контрпримера не остановилась за время , то она не остановится действительно никогда, но это может иметь разное значение: либо она нашла такое , что последовательность Коллатца для него будет бесконечной (и соответственно программа будет бесконечно долго вычислять её значения), либо же что она такого никогда не найдёт. Таким образом мы не получили из работы программы никакой полезной информации. Однако простая модификация алгоритма поиска контрпримера может всё таки дать нам способ понять найден ли контрпример либо же он не будет найден никогда. Придумайте эту модификацию.
Упражнение. Великая теорема Ферма (на английском её называют последней теоремой) утверждает, что уравнение
не имеет решений для . Покажите, как можно было бы её доказать при неограниченном вычислимом ресурсе и известных .
У Великой теоремы Ферма интересная история. Ферма сформулировал её в 1637 году в виде пометки на полях книги, которую он читал. Там же он указал, что он нашёл элементарное доказательство этой теоремы, но поскольку места недостаточно, он его приводить не будет. В итоге первое доказательство этой теоремы появилось только в 1994 году (спустя 356 лет!) и занимало оно 130 страниц. В этом доказательстве спустя год была найдена ошибка, которую ещё год исправляли. В итоге финальное доказательство без ошибки было представлено лишь в 1996 году. Такая вот история.
В заключение этого параграфа я замечу, что конечно всё написанное здесь очень неформально. Из написанного мной совершенно например не понятно каким образом компьютерные программы увязываются с аксиоматикой натуральных чисел и теорией множеств. Так же совершенно не понятно что именно из себя представляют сами программы. Использовать в качестве модели реальные компьютеры и реальные языки программирования не получится, так как в реальных системах есть много факторов, которые мешают идеализированному математическому описанию вычислительного процесса: механизмы мультипоточности, пайплайнинг, всякие мьютексы, графический интерфейс, ввод-вывод и вот всё подобное не дают оставляют возможности рассуждать о вычислениях абстрактно.
Чтобы избавиться от всех мешающих факторов реальных компьютеров используется модель машины Тьюринга, которая представляет собой видимо самую простую модель компьютера. Я не буду её описывать, но если вы хотите понять как примерно устроен формализм того что я здесь изложил, вы можете поискать статьи о ней и о том как она соответствует реальным вычислительным машинам, рекурсивным функциям и понятию множеств. Так же рекомендую почитать трагичную и важную биографию самого Алана Тьюринга: будучи блестящим учёным, работающим в области вычислимости и криптографии (благодаря значительным образом его работе союзники могли перехватывать шифрованные сообщения Германии во Вторую Мировую войну; теорема 3.50 так же была сформулирована и доказана именно им), он был обвинён в гомосексуальных связях и принуждён к химической кастрации и употреблению гормональных таблеток «для лечения гомосексуализма». Закончилась история лишением его всех военных наград и званий, а так же полным запретом на занятия накой. Итогом таких мер по борьбе с гомосексуализмом стал его суицид.
Сегодня ему ставят памятники, его именем названа самая престижная награда в области компьютерных наук. На этой трагичной ноте мы и закончим эту главу.
Этот текст последний параграф третьей главы учебника. Напомню, что учебник целиком доступен в pdf, что гораздо удобнее читать.