EssayAI
Блог
Блог
Математика и алгоритмы

Дополнительный код: представление отрицательных чисел

11 июня 2026Время чтения: 7 минут
#дополнительный код#представление отрицательных чисел#двоичная арифметика#информатика#системы счисления

Почти все современные процессоры хранят целые числа в дополнительном коде (two's complement). Это не произвольный выбор: именно он позволяет складывать положительные и отрицательные числа одной и той же схемой, без специального флага знака и без дополнительных схем вычитания. Ниже разберём, как устроено это представление, почему диапазон чисел несимметричен, как перевести любое отрицательное число и где чаще всего ошибаются студенты. Для наглядности используй калькулятор ниже: он показывает двоичный и шестнадцатеричный код, вклад каждого разряда и положение числа на «кольце» кодов.

Почему не просто добавить бит знака

Наивная идея: взять двоичное представление числа и добавить к нему отдельный бит знака (0 - плюс, 1 - минус). Называется такой формат «прямой код». Проблема: у нуля появляются два представления - 00000000200000000_2 и 10000000210000000_2 - и сложение с отрицательными числами требует отдельной схемы с вычитателем. Дополнительный код решает обе проблемы разом.

Анимация перехода от прямого кода к дополнительному: старший бит получает отрицательный вес -2^(N-1), и цепочка кодов «замыкается» в кольцо

Ключевая идея: старший разряд (MSB) получает отрицательный вес 2N1-2^{N-1}, остальные разряды сохраняют обычные положительные веса 2N2,2N3,,202^{N-2}, 2^{N-3}, \ldots, 2^0. Значение числа по NN-битному слову bN1bN2b0b_{N-1}b_{N-2}\ldots b_0 определяется формулой:

x=bN12N1+k=0N2bk2k.x = -b_{N-1}\cdot 2^{N-1} + \sum_{k=0}^{N-2} b_k \cdot 2^k.

Именно поэтому слово 10000000210000000_2 в 8 битах означает не 0-0, а 128-128: единица в знаковом разряде вносит вес 27=128-2^7 = -128, а все остальные биты нулевые.

Диапазон и несимметричность

В NN-битном дополнительном коде можно представить числа от 2N1-2^{N-1} до +2N11+2^{N-1}-1:

мин=2N1,макс=2N11.\text{мин} = -2^{N-1}, \qquad \text{макс} = 2^{N-1}-1.

Диапазон несимметричен: отрицательных чисел на одно больше, чем положительных. Так происходит потому, что ноль «занимает» место в положительной половине. Для 8 бит: от 128-128 до 127127, для 16 бит: от 32768-32768 до 3276732767, для 32 бит: от 2147483648-2\,147\,483\,648 до +2147483647+2\,147\,483\,647.

Эта несимметрия имеет практические последствия. В языке C стандарт гарантирует, что тип int вмещает как минимум [215,2151][-2^{15}, 2^{15}-1], а на большинстве 32-битных платформ - [231,2311][-2^{31}, 2^{31}-1]. Функции наподобие abs() и Math.abs() возвращают неожиданный результат для минимального значения, потому что его положительный аналог не помещается в тот же тип.

Вклад разрядов числа -45 в 8-битном дополнительном коде: знаковый бит вносит -128, единичные разряды 64, 16, 2, 1 в сумме дают -45
Вклад разрядов числа -45 в 8-битном дополнительном коде: знаковый бит вносит -128, единичные разряды 64, 16, 2, 1 в сумме дают -45

Показательный пример: 1-1 в 8 битах записывается как 11111111211111111_2 (все единицы), а 128-128 - как 10000000210000000_2 (только знаковый бит). Для 128-128 не существует положительного двойника той же разрядности: +128+128 уже не помещается в 8-битный диапазон и требует 9 битов.

Как переводить число в дополнительный код

Три эквивалентных способа.

Способ 1: через вычитание из 2N2^N. Для отрицательного числа xx его NN-битный дополнительный код - это беззнаковое значение 2Nx2^N - |x|.

Пример: 45-45 в 8 битах \Rightarrow 25645=211=110100112256 - 45 = 211 = 11010011_2.

Способ 2: инверсия плюс единица. Берём абсолютное значение x|x|, записываем его в двоичном виде, инвертируем все биты (заменяем 0 на 1 и наоборот), добавляем 1.

45=45=001011012инверсия110100102+1110100112|{-45}| = 45 = 00101101_2 \xrightarrow{\text{инверсия}} 11010010_2 \xrightarrow{+1} 11010011_2.

Способ 3: по весам разрядов. Строим единственную комбинацию битов bkb_k, при которой b7128+k=06bk2k=45-b_7\cdot128 + \sum_{k=0}^{6} b_k\cdot2^k = -45. Знаковый бит обязательно 1 (число отрицательное), тогда 128+S=45S=83=64+16+2+1-128 + S = -45 \Rightarrow S = 83 = 64+16+2+1, то есть b6b5b4b3b2b1b0=10100112b_6b_5b_4b_3b_2b_1b_0 = 1010011_2. Итог: 11010011211010011_2.

Все три способа дают одинаковый результат.

Обратное преобразование: из кода в число

Чтобы прочитать знаковое значение из дополнительного кода, можно воспользоваться той же формулой весов: если старший бит равен 1, число отрицательное; его величина считается как (2Nu)-(2^N - u), где uu - беззнаковая интерпретация того же слова.

Пример: 110100112=2111011010011_2 = 211_{10} (без знака). Знаковый бит единица, значит x=211256=45x = 211 - 256 = -45.

Этот алгоритм используют и процессоры, и компиляторы при разыменовании. Когда ты объявляешь переменную как знаковый тип - компилятор читает старший бит как отрицательный вес. Беззнаковый тип той же разрядности использует тот же битовый паттерн, но интерпретирует старший бит как обычный положительный вес +2N1+2^{N-1}. Именно поэтому приведение 1-1 (знаковый int8) к беззнаковому uint8 даёт 255255: те же биты 11111111211111111_2, другая интерпретация.

Сложение работает «само»

Главное преимущество дополнительного кода: сложение двух N-битных чисел с любыми знаками выполняется одной схемой обычного двоичного сумматора, а результирующий N-битный код даёт правильный ответ при отсутствии переполнения.

(45)+(+18)=110100112+000100102=111001012=27.(-45) + (+18) = 11010011_2 + 00010010_2 = 11100101_2 = -27.

Перенос из старшего разряда отбрасывается. Вычитание aba - b реализуется как a+(b)a + (-b), то есть сложение с дополнительным кодом bb. Чтобы получить b-b, достаточно инвертировать биты и прибавить 1 - ровно та же операция, что и при переводе в дополнительный код.

Это свойство позволяет процессору использовать единственный сумматор для всей целочисленной арифметики. В прямом коде потребовалось бы отдельное устройство для вычитания, отдельное для инвертирования знака и логика выбора между операциями. Дополнительный код устраняет эту сложность на аппаратном уровне, что стало одной из главных причин его повсеместного принятия в 1960-х годах.

Переполнение

Переполнение происходит, когда истинный результат операции выходит за диапазон NN-битного представления. Процессор обнаруживает его по флагу V (overflow): он устанавливается, когда знак сложения двух чисел с одинаковыми знаками не совпадает со знаком результата.

Пример: в 8 битах 100+50=150100 + 50 = 150, но 150>127150 > 127, поэтому код 10010110210010110_2 интерпретируется как 106-106 - неверный знаковый результат с установленным флагом V. Знаковый бит у обоих операндов равен 0 (оба положительны), а у результата равен 1 (отрицательный) - именно это несоответствие и сигнализирует переполнение.

Переполнение при сложении двух положительных 8-битных чисел: сумма 100+50=150 выходит за диапазон +127 и обёртывается в -58; флаг V отражает несовпадение знаков операндов и результата

Правило детекции переполнения при сложении: если оба слагаемых положительны и сумма отрицательна (или оба отрицательны и сумма положительна) - произошло переполнение. При вычитании и при сложении разных знаков переполнение невозможно.

Частые ошибки

  • Инвертировать биты, но забыть прибавить единицу. Результат после инверсии - обратный код (ones' complement), а не дополнительный. Без +1 значение на 1 больше нужного.
  • Перепутать диапазон. В 8 битах минимум 128-128, максимум +127+127, а не ±127\pm127. Попытка записать +128+128 в int8_t - UB в C/C++ и переполнение в аппаратной арифметике.
  • Читать код без учёта разрядности. Код 111121111_2 в 4 битах означает 1-1, но 00001111200001111_2 в 8 битах - это +15+15. Разрядность контекста критична.
  • Вычислять дополнение от нуля как 2N2^N. Число 0 в дополнительном коде - ровно NN нулей; специального кода для 0-0 нет.
  • Переполнение при смене знака 2N1-2^{N-1}. Это единственное число, для которого операция «взять противоположное» не работает в той же разрядности: (128)=+128-(-128) = +128 не помещается в 8-битный int.

FAQ

Чем дополнительный код отличается от прямого и обратного? Прямой код хранит знак отдельным битом, имеет два нуля и требует специального вычитателя. Обратный код получается инверсией всех битов модуля; у него тоже два нуля (0000000000000000 и 1111111111111111), и сложение требует циклического переноса. Дополнительный код = обратный + 1, один ноль, обычный двоичный сумматор. Именно поэтому все современные архитектуры (x86, ARM, RISC-V) используют дополнительный код для знаковых целых.

Как узнать знак числа по дополнительному коду, не разворачивая вычисления? Достаточно посмотреть на старший (левый) бит: 0 - число неотрицательное, 1 - отрицательное. Это прямое следствие того, что старший бит имеет отрицательный вес 2N1-2^{N-1}.

Почему отрицательных чисел на одно больше, чем положительных? Ноль требует своего представления. Все 2N2^N возможных кодов распределяются так: один под ноль, 2N112^{N-1}-1 под положительные и 2N12^{N-1} под отрицательные. «Лишнее» отрицательное число - это 2N1-2^{N-1}, у которого нет симметричного двойника в той же разрядности.

Коротко

Дополнительный код - стандартный способ хранить знаковые целые в компьютере: старший разряд получает отрицательный вес 2N1-2^{N-1}, остальные - обычные положительные веса. Диапазон N-битного числа: от 2N1-2^{N-1} до 2N112^{N-1}-1. Отрицательное число получают тремя путями: формулой 2Nx2^N - |x|, инверсией плюс единица или подбором по весам. Главное преимущество формата - сложение и вычитание реализуются единой схемой без дополнительной логики знака.

Доверьте текст нейросети EssayAI

Открыть EssayAI

Бесплатно, на русском языке и без VPN

Читайте также