Старший бит называется знаковым — 0 для положительных чисел и 1 для отрицательных, а остальные биты являются значащими. Традиционно для представления отрицательных чисел используются обратный и дополнительный коды, а вычитание заменяется сложением. При этом обратный код является инверсией прямого кода, а дополнительный образуется прибавлением положительной единицы к обратному коду. Недостатком прямого и обратного кодов считается наличие двух нулей. Для прямого кода в случае однобайтных чисел это 00000000 (положительный) и 10000000 (отрицательный). Для обратного кода положительный нуль 00000000, а отрицательный нуль 11111111. Для дополнительного кода только один нуль — 00000000.

Однако обратный код обладает неустранимым недостатком в обработке нулей. При вычислениях, когда результат равен нулю, мы всегда получаем отрицательный нуль (11111110 + 00000001 = 11111111). Положительный нуль получается только в случае сложения отрицательного нуля с единицей (11111111 + 00000001 = 00000000), что абсолютно некорректно с точки зрения математики. Поэтому обратный код будет рассматриваться как дополнение к прямому.

Для простоты изложения и понимания рассматривается вычитание только положительных чисел.

Прямое двоичное вычитание производится аналогично сложению. Оба варианта приведены в таблице 1.

Таблица 1. Прямое двоичное сложение и вычитание (x + y)

Случай 1Случай 2Случай 3Случай 4
x0011
y0101
Сумма0110
Перенос0001

Таблица 1. Прямое двоичное сложение и вычитание (x − y)

Случай 1Случай 2Случай 3Случай 4
x0011
y0101
Разность0110
Перенос0100

Как видно из таблицы 1, для расчёта суммы и разности используется одна и та же битовая операция XOR, различие операций только в методе формирования бита переноса.

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

Таблица 2. Замена операций для операндов разных знаков (x + y)

Знак xЗнак yЗамена
++x + y
+x − (−y)
+x − (−y)
x + y

Таблица 2. Замена операций для операндов разных знаков (x − y)

Знак xЗнак yЗамена
++x − y
+x + (−y)
+x + (−y)
x − y

Как указывалось выше, стандартно вычитание заменяется сложением, при этом второй операнд y преобразуется в дополнительный код инверсией всех бит с дальнейшим добавлением к нему положительной единицы. Результат всегда представлен в правильном (прямом или дополнительном) коде. Операция добавления положительной единицы не является отдельной операцией, она осуществляется как бит переноса 1 для младшего бита в основной операции сложения. Знаковые биты обрабатываются таким же образом, как и значащие.

Вся операция занимает один такт процессора. Это достоинство дополнительного кода — самые быстрые операции сложения и вычитания.

Но нет достоинств без недостатков. К недостаткам дополнительного кода относится несимметричный диапазон отрицательных и положительных чисел, поэтому для однобайтных чисел требуется отдельная обработка числа -128 (двоичная запись 10000000). Вычисление модуля этого числа нельзя представить в прямом коде однобайтного числа, также его формирование не подчиняется общим правилам формирования чисел в дополнительном коде. Это требует обработки этого случая либо в микрокоде процессора, либо на уровне рантайма программы. Отсутствие двух нулей приводит к тому, что математические программы вынуждены в расчётах эмулировать отрицательный нуль, что делается на уровне рантайма. Такие дополнительные проверки и эмуляции снижают скорость работы программы. Также к недостаткам дополнительного кода можно отнести то, что проверка на переполнение результата при сложении осуществляется не только по биту переноса из старшего значащего бита в знаковый бит, но и по биту переноса из знакового бита.

В прямом коде знаковый бит обрабатывается не так, как значащие биты: операция XOR проводится между знаковым битом операнда x и битом переноса из старшего значащего бита в знаковый бит.

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

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

Решением этой задачи является одновременное вычитание по правилам, изложенным в таблице 1, для операндов в прямом и обратном кодах с выбором результата по отсутствию бита переноса из старшего значащего бита в знаковый бит. Поясним это на примере: в прямом коде 25 − 47 = 00011001 − 00101111 = 11101010. Поскольку есть перенос из старшего значащего бита в знаковый бит, этот результат отбрасывается (даже несмотря на то, что знак результата правильный). В обратном коде тот же результат 25 − 47 = 11100110 − 11010000 = 10010110. Нет переноса из старшего значащего бита в знаковый бит, поэтому этот результат выдаётся как результат расчёта. Для отрицательных чисел аналогичные вычисления по вышеизложенным правилам показывают, что в прямом коде (−25) − (−47) = 10011001 − 10101111 = 01101010 (результат отбрасывается, несмотря на правильный знак результата), для обратного кода 01100110 − 01010000 = 00010110 (результат выдаётся как результат расчёта).

Из вышесказанного может показаться, что для реализации данного решения необходимо два вычитающих устройства. Но если вспомнить, что $x \oplus y = y \oplus x = (\lnot x) \oplus (\lnot y) = (\lnot y) \oplus (\lnot x)$, то схема формирования результата будет одинакова. Будут вычисляться два результата по прямым и инвертированным битам исходных операндов и два бита переноса. В конце расчёта выбирается один из результатов по отсутствию бита переноса из старшего значащего бита в знаковый бит.

Реализация этого алгоритма позволит осуществлять операцию вычитания в прямом коде за один такт процессора.

Программная эмуляция показала, что увеличение времени выполнения вычитания в прямом коде по сравнению с временем вычитания в дополнительном коде составляет не более 10%. Это вызвано последовательным выполнением действий. Учёт обработки переполнений при сложении уменьшает этот разрыв ещё на 2–3%. В случае параллельности выполнения действий различия во времени не будет.

К достоинствам прямого кода относится и более лёгкое детектирование переполнения результата при сложении: достаточно только бита переноса из старшего значащего бита в знаковый бит. Также к ним относится симметричный диапазон отрицательных и положительных чисел, что приводит к отсутствию вырожденных чисел и их обработки в рантайме. Обработка двух нулей нужна только при выводе результата на внешнее устройство (например, экран или файл).

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