Дополнительный код: как байт хранит отрицательные числа
В проводе нет знака «минус». Есть только высокое и низкое напряжение, 1 и 0. Откуда же в 8-битном процессоре берётся −5, если байт умеет хранить лишь числа от 0 до 255? Для этого инженеры придумали соглашение, которое называется дополнительным кодом (two's complement). Оно настолько удачное, что все современные процессоры хранят отрицательные числа именно так.
Проблема: в проводе нет минуса
Простейшее решение — отдать один бит под знак: старший бит 0 — число положительное, 1 — отрицательное. Такой «прямой код» понятно читается человеком, но ломает железо: сложение байта +5 и −5 в прямом коде не даёт нуля, и сумматору понадобилась бы отдельная логика для знаков — другой, более медленный тракт.
Дополнительный код устраняет проблему в корне: отрицательные числа устроены так, что обычный сумматор складывает их правильно, даже не зная о знаках. Отдельный вычитатель в процессоре не нужен вообще.
Как получить −N: инверсия и +1
Правило для 8-битного байта:
1. Записать число N в обычном двоичном виде: 5 = 00000101.
2. Инвертировать все восемь битов (НЕ над каждым): 11111010.
3. Прибавить единицу: 11111010 + 1 = 11111011.
Готово: 11111011 — это −5. Обратный перевод тот же: инверсия и +1 (11111011 → 00000100 → 00000101 = 5). Старший бит у отрицательных чисел всегда 1 — он «бесплатно» работает индикатором знака, и именно его проверяет флаг Negative из статьи о флагах состояния.
Колесо значений: от −128 до 127
Удобнее всего почувствовать дополнительный код как колесо: байт умеет 256 значений, и мы просто разрезали круг не 0…255, а так: 0…127 — положительные, дальше 128…255 читаются как −128…−1. Прибавляя единицу, мы движемся по колесу; 127 + 1 = −128 — «переехали» через границу, это и есть переполнение знака (его ловит отдельный флаг в настоящих процессорах).
Из-за такой разметки диапазон несимметричен: положительных 127, а отрицательных 128. Ноль один — у него единственное представление, в отличие от прямого кода, где +0 и −0 записывались по-разному.
Вычитание через сложение
Теперь главное следствие: A − B = A + (−B). Чтобы вычесть 10 из 25 в 8-битном АЛУ, процессор берёт −10 (11110110), подаёт оба байта на тот же самый сумматор и получает 00001111 — пятнадцать. Никакой новой схемы: те же полные сумматоры, что складывают обычные числа.
Именно так устроен уровень «Проверка на минус» в курсе: программа вычисляет 5 − 10, получает 251 (= −5 в дополнительном коде), а флаги N и Z показывают, что результат ушёл в минус. И тем же приёмом пользуется ассемблер: команды вычитания там нет — есть ADD отрицательного числа.
Проверить любой байт в дополнительном коде можно в конвертере систем счисления — он показывает знак и диапазон.
Проверь себя
Как записать −1 в дополнительном коде одного байта?
Инверсия 00000001 даёт 11111110, прибавляем 1 — 11111111. Минус один — это все единицы.
Почему вычитатель не нужен отдельной схемой?
Потому что A − B = A + (−B), а −B получается инверсией и +1. Обычный сумматор складывает их корректно без какой-либо знаковой логики.
Что случится, если к 127 прибавить 1 в знаковом байте?
Получится −128: колесо переполнено. Положительных значений ровно 127, и шаг за их границу «прыгает» на самый низ диапазона.