Забытая компьютерная арифметика: от тритов «Сетуни» и двух нулей «Аполлона» до HEX-магии Кармака

Прокомментировать Просмотры: 9

CDC 6600, Steve Jurvetson из Menlo Park, USA. Flickr

Сможете ли вы без долгих раздумий объяснить, по какой причине простейшее сложение 0.1 + 0.2 в JavaScript возвращает значение 0.30000000000000004?

В среде разработчиков подобные странности нередко списывают на некие «врожденные капризы» конкретного языка программирования: мол, такова его специфика, и с этим ничего не поделать.

Разумеется, отмахнуться куда легче, чем потратить несколько минут на изучение первопричин. Однако за каждым подобным поведением кроется фундаментальное противоречие между непрерывным человеческим восприятием и строгой дискретной логикой кремниевых чипов. Мы оперируем привычными десятичными величинами в удобных редакторах кода, тогда как на уровне микроархитектуры разворачивается напряженная борьба с ограничениями машинной арифметики.

В данном материале мы исследуем эволюцию цифровых вычислений: от нетривиальных инженерных решений в навигационном компьютере корабля «Аполлон» до грядущего кризиса 2038 года. Устраивайтесь поудобнее — впереди детальный разбор.

Эпоха нестандартных машинных слов

Сегодня 8-битный байт воспринимается инженерами как нечто само собой разумеющееся — базовая константа цифрового мира, сопоставимая с фундаментальными физическими величинами. Тем не менее на заре вычислительной эры концепция побайтовой адресации отсутствовала вовсе. Архитектуры того времени опирались на машинные слова произвольной разрядности, диктуемые конкретными задачами.

К примеру, в суперкомпьютере CDC 6600 элементарная ячейка памяти насчитывала 60 бит. Для обработки текстовой информации инженерам приходилось программно расщеплять это массивное слово на отдельные 6-битные сегменты. В советской ЭВМ «Минск-32» разрядность слова составляла 37 бит (36 информационных разрядов плюс один контрольный), а архитектура популярного миникомпьютера PDP-8 базировалась на 12-битных словах.

ЭВМ «Минск-32». На Минском ордена Ленина заводе ЭВМ им. Орджоникидзе
ЭВМ «Минск-32». На Минском ордена Ленина заводе ЭВМ им. Орджоникидзе

Поворотной точкой стал 1964 год, ознаменовавшийся дебютом семейства мейнфреймов IBM System/360. Разработчики стремились спроектировать универсальную платформу, одинаково пригодную как для сложных инженерных симуляций, так и для корпоративного учета. Коммерческому сектору требовалась эффективная обработка структурированного текста: наименований, реквизитов и персональных данных клиентов.

Ben Franske. DM IBM S360.jpg on en.wiki
Ben Franske. DM IBM S360.jpg on en.wiki

Применявшийся ранее 6-битный двоично-десятичный код (BCD) предоставлял всего 64 уникальные комбинации. Этого едва хватало на цифры и базовую латиницу в верхнем регистре, тогда как делопроизводство требовало строчных букв, знаков препинания и вспомогательных управляющих символов.

В архитектуре System/360 инженеры закрепили размер байта равным 8 битам, внедрив кодировку EBCDIC. Данное решение предопределило развитие индустрии на десятилетия вперед, превратив октет в отраслевой канон. Впрочем, адаптация восточноазиатских иероглифических систем письма по-прежнему оставалась сложнейшим инженерным вызовом.

Сам термин «байт» был введен специалистом IBM Вернером Бухгольцем еще в 1956 году как намеренно видоизмененное слово bite («порция, кусочек данных»). Благодаря беспрецедентному рыночному успеху линейки System/360 8-битная структура стала мировым стандартом de facto.

Тем не менее унификация разрядности не устранила когнитивный барьер: восприятие бинарных последовательностей человеком оставалось крайне затруднительным. Достаточно попробовать мгновенно перевести значение 11010111₂ в десятичный формат, чтобы оценить сложность ручной отладки того времени. Это подтолкнуло ученых к поиску более выразительных систем счисления.

Современный инструментарий разработчика обычно ограничен четырьмя базисами: двоичным, восьмеричным, десятичным и шестнадцатеричным. Однако история компьютерных технологий хранит немало примеров куда более оригинальных математических парадигм.

«Аполлон-11»: парадокс знакового нуля

Бортовой управляющий компьютер AGC (Apollo Guidance Computer), обеспечивший историческую посадку на поверхность Луны в 1969 году, служит наглядной демонстрацией инженерных компромиссов. В условиях строжайшей экономии массы и энергопотребления инженеры NASA использовали 15-битную архитектуру, где отрицательные величины кодировались с помощью обратного кода (ones’ complement). Следствием этого стало появление двух формальных представлений нуля: «+0» (000000) и «-0» (111111).

В процессорах операция вычитания традиционно реализуется через сложение с инвертированным представлением вычитаемого, что избавляет от необходимости проектировать отдельные вычислительные блоки. Старший бит при этом выполняет роль знакового: 0 соответствует положительным значениям, 1 — отрицательным. Для положительных величин прямой, обратный и дополнительный коды идентичны (например, +5 в 4-битной сетке — это 0101).

Отрицательное число -5 в прямом коде выглядит как 1101. Поразрядное инвертирование дает обратный код 1010. Главный недостаток прямого и обратного кодирования — неоднозначность нуля, представленного как 0000 (+0) и 1000 либо 1111 (-0). Это вынуждает процессор расходовать такты на двойную проверку нулевого состояния.

Эту проблему устраняет дополнительный код (two’s complement), ставший современным стандартом: прибавление единицы к обратному коду (1010 + 1 = 1011) ликвидирует дублирующий «минусовой» ноль, сворачивая его в стандартный 0000, что упрощает суммирование любых знаковых чисел на аппаратном уровне.

Однако в эпоху программы «Аполлон» навигационный алгоритм мог сложить противоположные величины и получить результат «-0». Для систем управления маневровыми двигателями программное различие между «нулевой скоростью» и «вектором торможения с нулевым модулем» могло стать фатальным. Специалистам лаборатории MIT пришлось интегрировать вспомогательные алгоритмические проверки, исключающие рассинхронизацию физической модели и логики управления.

ЭВМ «Сетунь»: триты и троичная логика

В 1959 году в Московском государственном университете под руководством выдающегося инженера Николая Брусенцова была спроектирована уникальная ЭВМ «Сетунь», функционировавшая на базе симметричной троичной системы счисления. Фундаментальной единицей данных в ней выступал трит, принимавший три дискретных состояния: -1, 0 и +1.

USSR state-owned publisher "Sputnik". https://www.alamy.com/stock-photo-scientific-staff-members-working-on-the-computing-machine-setun-22818602.html
USSR state-owned publisher «Sputnik». https://www.alamy.com/stock-photo-scientific-staff-members-working-on-the-computing-machine-setun-22818602.html

Дефицит серийной вычислительной техники в СССР побудил академика Сергея Соболева инициировать разработку экономичной и неприхотливой машины для исследовательских лабораторий и учебных процессов.

Столкнувшись с ненадежностью электронных ламп, коллектив Брусенцова реализовал логику на миниатюрных ферритовых сердечниках и полупроводниковых диодах. Математический анализ показал, что основание системы счисления, близкое к числу Эйлера e ≈ 2.718 (то есть тройка), обеспечивает максимальную плотность записи данных при минимальных аппаратных затратах.

Госиспытания 1960 года зафиксировали феноменальный показатель: полезное эксплуатационное время «Сетуни» достигло 95%, в то время как отраслевой нормой тех лет считались скромные 60%.

Машина не нуждалась в служебных знаковых битах: знак числа естественным образом определялся старшим ненулевым тритом. Кроме того, трехзначная логика прекрасно подходила для задач предикатного анализа и вероятностного моделирования.

Несмотря на высокую надежность и технологические преимущества, проект выпустили тиражом всего в 50 экземпляров. Ведомственные разногласия и последующий курс на стандартизацию зарубежных двоичных архитектур привели к преждевременному сворачиванию производства и постепенному забвению троичного направления.

Шестнадцатеричная магия и оптимизация Кармака

С переходом к 32-битным архитектурам шестнадцатеричная нотация (Hex) окончательно закрепилась в качестве основного формата представления машинных данных. Один байт компактно описывается парой символов в диапазоне от 00 до FF, а 32-битные адреса памяти сворачиваются в аккуратные конструкции формата 0x7FFF.

Каждый Hex-разряд строго соответствует 4 битам (полубайту, или нибблу), сохраняя прозрачность битовой структуры. Так, установка старшего знакового бита в 32-разрядном слове наглядно выражается через 0x80000000, тогда как его десятичный эквивалент 2147483648 полностью скрывает двоичную топологию.

Классическим примером виртуозной работы с двоичным представлением чисел стал алгоритм быстрого вычисления обратного квадратного корня из движка игры Quake III Arena (1999). Расчет трехмерного освещения и векторных отражений требовал сотен тысяч подобных вычислений за кадр, а аппаратное деление чисел с плавающей запятой на тогдашних процессорах было чрезмерно ресурсоемким.

Стандартная процедура 1 / √x на сопроцессорах тех лет отнимала десятки тактов. Решение, примененное Джоном Кармаком, базировалось на глубоком понимании формата представления чисел IEEE 754 и побитовых манипуляциях с последующим уточнением по методу Ньютона:

float Q_rsqrt( float number ) {

long i;

float x2, y;

const float threehalfs = 1.5F;

x2 = number * 0.5F;

y = number;

i = ( long ) &y;

i = 0x5f3759df — ( i >> 1 );

y = ( float ) &i;

y = y ( threehalfs — ( x2 y * y ) );

return y;

}

Ключевой этап алгоритма — выражение i = 0x5f3759df - ( i >> 1 );. Значение типа float интерпретировалось как целое число, подвергалось битовому сдвигу вправо и вычиталось из специально подобранной шестнадцатеричной константы.

Это давало поразительно точное начальное приближение обратного корня, погрешность которого после единственной итерации метода Ньютона составляла всего ~0,17% — более чем достаточно для рендеринга графики.

После открытия исходного кода Quake III в 2005 году выяснилось, что истоки формулы восходят к 1980-м годам. Ее первоначальную концепцию сформулировал Грег Уолш из Ardent Computer, а точное значение константы вычисляли Клив Моулер (создатель MATLAB) и Гэри Таролли (сооснователь 3dfx Interactive). Заслуга Кармака заключалась в своевременной и безупречной интеграции этого метода в графический конвейер реального времени.

Восьмеричная система в недрах UNIX

Восьмеричная нотация (Octal), доминировавшая в эпоху систем с 12- и 36-битными машинными словами, со временем уступила первенство шестнадцатеричному представлению. Однако она прочно закрепилась в POSIX-стандартах для разграничения файловых прав.

Синтаксис команды chmod 755 опирается именно на восьмеричный базис. Модель безопасности Unix разделяет права на три категории субъектов (владелец, группа, остальные), каждая из которых содержит три атомарных атрибута: чтение (r), запись (w) и выполнение (x).

Каждая группа прав представляет собой 3-битный блок. Значение 111₂ формирует восьмеричную 7 (полный доступ rwx), а 101₂ трансформируется в 5 (чтение и запуск r-x). Восьмеричный базис естественным образом покрывает трехбитную структуру без избыточных разрядов, гарантируя лаконичность синтаксиса.

Проблема 2038 года (Unix Epochalypse)

Несмотря на терабайты оперативной памяти и переход на 64-битные регистры, миллионы устройств по-прежнему подвержены скрытому риску переполнения счетчика системного времени, известному как проблема Y2K38.

В Unix-подобных ОС отсчет времени ведется в секундах, прошедших с 1 января 1970 года (Unix Epoch). В устаревших спецификациях тип time_t определен как 32-битное знаковое целое. С учетом знакового разряда предельное значение счетчика составляет 2³¹ — 1, или 2 147 483 647 секунд.

Критический предел наступит 19 января 2038 года в 03:14:07 по UTC. В этот момент битовая маска примет значение 01111111 11111111 11111111 11111111.

Следующий секундный тик вызовет арифметическое переполнение: знаковый бит установится в 1, превращая значение в -2 147 483 648 (10000000 00000000 00000000 00000000). Для системы время мгновенно сместится на 13 декабря 1901 года, провоцируя сбои в планировщиках задач, валидации сертификатов и работе СУБД.

В то время как десктопные и серверные платформы перешли на 64-битный формат time_t (запас которого исчисляется миллиардами лет), в сегменте IoT и встраиваемых микроконтроллеров угроза сохраняется.

Для 32-битных контроллеров без поддержки 64-битной арифметики применяется перевод счетчика в беззнаковый формат (unsigned), что отодвигает порог переполнения до 7 февраля 2106 года за счет утраты поддержки дат до 1970 года.

В промышленных системах с закрытым микрокодом используется временное окно (Time Windowing), когда уровень абстракции искусственно смещает базу времени на несколько десятилетий назад, компенсируя сдвиг на этапе прикладной обработки.

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

Мы научили машины оперировать символами, задействовали низкоуровневые трюки для имитации сложной физики и лаконично упаковали права доступа. Однако базовые принципы функционирования регистров остаются неизменными.

С развитием квантовых вычислений на смену классическим битам придут кубиты, кардинально изменив подход к обработке данных. Но до тех пор фундаментальное преимущество остается за разработчиками, которые глубоко понимают, как их высокоуровневый код транслируется в элементарные битовые операции на кремниевом кристалле.

P.S.

Погрешность вычисления 0.1 + 0.2 === 0.30000000000000004 в JavaScript обусловлена стандартом IEEE 754 для чисел с плавающей точкой двойной точности (Float64). Дроби 0.1 и 0.2 в двоичной системе счисления превращаются в бесконечные периодические дроби.

Поскольку разрядность мантиссы ограничена 53 битами, числа округляются при сохранении в память. Сложение двух приближенных величин приводит к накоплению погрешности в младших разрядах, формируя артефакт в конце десятичной записи.

 

Источник

Поделиться:

Похожие статьи

Поиск по играм, новостям и статьям…

Введите не менее двух символов

Введите не менее двух символов