Текст работы
Старший бит называется знаковым – 0 для положительных чисел и 1 для отрицательных, а остальные биты являются значащими. Традиционно для представления отрицательных чисел используются обратный и дополнительный коды, а вычитание заменяется сложением. При этом обратный код является инверсией прямого кода, а дополнительный образуется прибавлением положительной единицы к обратному коду. Недостатком прямого и обратного кодов считается наличие двух нулей. Для прямого кода в случае однобайтных чисел это 00000000 (положительный) и 10000000 (отрицательный). Для обратного кода положительный нуль 00000000, а отрицательный нуль 11111111. Для дополнительного кода только один нуль – 00000000.
Однако обратный код обладает неустранимым недостатком в обработке нулей. При вычислениях, когда результат равен нулю, мы всегда получаем отрицательный нуль ($11111110 + 00000001 = 11111111$). Положительный нуль получается только в случае сложения отрицательного нуля с единицей ($11111111 + 00000001 = 00000000$), что абсолютно некорректно с точки зрения математики. Поэтому обратный код будет рассматриваться как дополнение к прямому.
Для простоты изложения и понимания рассматривается вычитание только положительных чисел.
Прямое двоичное вычитание производится аналогично сложению. Оба варианта приведены в таблице 1.
Таблица 1. Исходная операция
| $x + y$ | $x - y$ | ||||||||
|---|---|---|---|---|---|---|---|---|---|
| $x$ | 0 | 0 | 1 | 1 | $x$ | 0 | 0 | 1 | 1 |
| $y$ | 0 | 1 | 0 | 1 | $y$ | 0 | 1 | 0 | 1 |
| Сумма | 0 | 1 | 1 | 0 | Разность | 0 | 1 | 1 | 0 |
| Перенос | 0 | 0 | 0 | 1 | Перенос | 0 | 1 | 0 | 0 |
Как видно из таблицы 1, для расчёта суммы и разности используется одна и та же битовая операция XOR, различие операций только в методе формирования бита переноса.
Особенностью вычислений в прямом коде является необходимость проведения операций только с операндами одного знака. Поэтому для операндов разных знаков приходится применять замену операций, показанную в таблице 2.
Таблица 2. Исходная операция
| $x + y$ | $x - y$ | ||||||||
|---|---|---|---|---|---|---|---|---|---|
| Операнды | Знаки операндов | Операнды | Знаки операндов | ||||||
| $x$ | $+$ | $+$ | $-$ | $-$ | $x$ | $+$ | $+$ | $-$ | $-$ |
| $y$ | $+$ | $-$ | $+$ | $-$ | $y$ | $+$ | $-$ | $+$ | $-$ |
| Замена | $x + y$ | $x - (-y)$ | $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 \operatorname{XOR} y = y \operatorname{XOR} x = (\operatorname{NOT} x) \operatorname{XOR} (\operatorname{NOT} y) = (\operatorname{NOT} y) \operatorname{XOR} (\operatorname{NOT} x)$, то схема формирования результата будет одинакова. Будут вычисляться два результата по прямым и инвертированным битам исходных операндов и два бита переноса. В конце расчёта выбирается один из результатов по отсутствию бита переноса из старшего значащего бита в знаковый бит.
Реализация этого алгоритма позволит осуществлять операцию вычитания в прямом коде за один такт процессора.
Программная эмуляция показала, что увеличение времени выполнения вычитания в прямом коде по сравнению с временем вычитания в дополнительном коде составляет не более 10 %. Это вызвано последовательным выполнением действий. Учёт обработки переполнений при сложении уменьшает этот разрыв ещё на 2-3 %. В случае параллельности выполнения действий различия во времени не будет.
К достоинствам прямого кода относится и более лёгкое детектирование переполнения результата при сложении: достаточно только бита переноса из старшего значащего бита в знаковый бит. Также к ним относится симметричный диапазон отрицательных и положительных чисел, что приводит к отсутствию вырожденных чисел и их обработки в рантайме. Обработка двух нулей нужна только при выводе результата на внешнее устройство (например, экран или файл).
Выбор между прямым и дополнительным кодами должен проводиться с учётом всех достоинств и недостатков использования каждого кода.