5. АРИФМЕТИЧЕСКИЕ ОПЕРАЦИИ НАД ЦЕЛЫМИ ДВОИЧНЫМИ ЧИСЛАМИ СО ЗНАКОМ
5.1. Общие сведения об арифметических операциях над числами со знаком
Рассмотрим особенности каждого из четырех основных арифметических действий (сложение, вычитание, умножение и деление) для двоичных чисел со знаком.
Особенности арифметических действий для двоичных чисел без знака, включая сложение и вычитание операндов большой размерности, рассмотрены в уч. пос., ч.1.
5.2. Сложение двоичных чисел со знаком
Микропроцессор не подозревает о различии между числами со знаком и без знака. Вместо этого у него есть средства фиксирования возникновения характерных ситуаций, складывающихся в процессе вычислений. Некоторые из них рассмотрены при обсуждении сложения чисел без знака:
флаг переноса cf, установка которого в 1 говорит о том, что произошел выход за пределы разрядности операндов;
команда adc, которая учитывает возможность такого выхода (перенос из младшего разряда).
Другое средство – это регистрация состояния старшего (знакового) разряда операнда, которое осуществляется с помощью флага переполнения of (бит 11).
Итак, числа в компьютере представляются следующим образом: положительные - в прямом коде, отрицательные - в дополнительном коде. Рассмотрим различные варианты сложения чисел микропроцессором 8086 для случая, когда оба операнда являются словами, т.е.занимают по 16 битов каждый. Примеры призваны показать поведение двух старших битов операндов и правильность результата операции сложения.
Пример 1.
30566 = 01110111 01100110
+
41
00687 = 00000010 10101111
=
31253 = 01111010 00010101
Следим за переносами из 14-го и 15-го разрядов и правильностью результата: переносов нет, результат правильный.
Пример 2.
30566 = 01110111 01100110
+
30566 = 01110111 01100110
=
61132 = 11101110 11001100
Произошел перенос из 14-го разряда; из 15-го разряда переноса нет. Результат неправильный, так как имеется переполнение - значение числа получилось больше, чем то, которое может иметь 16битное число со знаком (+32 767).
Пример 3.
-30566 = 10001000 10011010
+
-04875 = 11101100 11110101
=
-35441 = 01110101 10001111
Из 14-го разряда нет переноса, а из 15-го разряда произошел перенос. Результат неправильный, так как вместо отрицательного числа получилось положительное (в старшем бите находится 0).
Пример 4.
-4875 = 11101100 11110101
+
-4875 = 11101100 11110101
=
-9750 = 11011001 11101010
Есть переносы из 14-го и 15-го разрядов. Результат правильный. Таким образом, переполнения не происходит (то есть флагу of присваивается 0), если есть перенос из обоих разрядов или из 14-го и
из 15-го разрядов перенос отсутствует.
И наоборот, переполнение (установка of в 1) происходит при переносе:
из 14-го разряда (для положительных чисел со знаком);
из 15-го разряда (для отрицательных чисел).
Дополнительно к флагу of при переносе из старшего разряда
устанавливается в 1 и флаг переноса cf. Так как процессор не знает о
42
существовании чисел со знаком и без знака, то вся ответственность за правильность действий с получившимися числами ложится на программиста. Проанализировать флаги cf и of можно командами условного перехода jc\jnc и jo\jno соответственно.
Сложение двух операндов, занимающих по 8 битов (т.е.сложение байтов), аналогично, но переполнение возникает при переносе из 6-го разряда для положительных и из 7-го разряда для отрицательных чисел. Для процессора 80386 и выше кроме этих двух случаев возможно сложение двойных слов. Переполнение при таком сложении возникает при переносе из 30-го для положительных и из 31-го – для отрицательных чисел.
Что же касается команд сложения чисел со знаком, то они те же, что и для чисел без знака.
5.3. Вычитание двоичных чисел со знаком
Процессору незачем иметь два устройства - сложения и вычитания. Достаточно наличия только одного - устройства сложения. Но для вычитания способом сложения чисел со знаком необходимо представлять в дополнительном коде оба операнда - и уменьшаемое, и вычитаемое. Результат тоже нужно рассматривать как значение в дополнительном коде. Здесь возникают сложности. Прежде всего, они связаны с тем, что старший бит операнда рассматривается как знаковый. Рассмотрим пример вычитания отрицательного числа из положительного:
45 = 0010 1101
-
-127 = 1000 0001
=
-44 = 1010 1100
Судя по знаковому разряду, результат получился отрицательный, что, в свою очередь, говорит о том, что число нужно рассматривать как дополнение, равное –44. Правильный результат должен быть равен 172. Здесь мы, как и в случае знакового сложения, встретились с переполнением, когда значащий разряд числа изменил знаковый разряд операнда. Отследить такую ситуацию можно по содержимому флага переполнения of. Его установка в 1 говорит о том, что результат вышел за диапазон представления знаковых чисел (то есть изменился старший бит) для операнда данного размера, и
43
программист должен предусмотреть действия по корректировке результата.
Пример вычитания положительного числа из отрицательного числа как сложение двух отрицательных чисел: -45 - 45 = -45 + (-45)= -90.
-45 = 1101 0011
+
-45 = 1101 0011
=
-90 = 1010 0110
Здесь все нормально, флаг переполнения of сброшен в 0, а 1 в знаковом разряде говорит о том, что значение результата – число в дополнительном коде.
5.4. Умножение чисел со знаком
5.4.1. Эта команда имеет три формы, различающиеся числом операндов:
imul источник
imul приемник, источник
imul приемник, источник1, источник2
5.4.2. Если используется форма imul источник,
то источник (регистр или переменная) умножается на al или ax (в зависимости от размера операнда) и результат располагается в ax или dx:ax соответственно.
5.4.3. Если используется форма imul приемник, источник,
то источник (число, регистр или переменная) умножается на приемник (регистр) и результат заносится в приемник.
5.4.4. Если используется форма imul приемник, источник1, источник2,
то источник 1 (регистр или переменная) умножается на источник 2 (число) и результат заносится в приемник (регистр).
5.4.5.Во всех трех вариантах считается, что результат может занимать в два раза больше места, чем размер источника. В первом случае приемник автоматически оказывается достаточно большим, но во втором и третьем случаях могут произойти переполнение и потеря старших битов результата.
5.4.6.Флаги of и cf будут равны:
44
единице, если это произошло;
нулю, если результат умножения поместился целиком в приемник (во втором и третьем случаях) или в младшую половину приемника (в первом случае).
Значения флагов sf, zf, af и pf после команды imul не определены.
5.4.7. Эта команда выполняется так же, как и команда mul. Отличительной особенностью команды imul является только формирование знака.
Если результат мал и умещается в одном регистре (то есть если cf = of = 0), то содержимое другого регистра (старшей части) является расширением знака - все его биты равны старшему биту (знаковому разряду) младшей части результата.
Впротивном случае (если cf = of = 1) знаком результата является знаковый бит старшей части результата, а знаковый бит младшей части является значащим битом двоичного кода результата.
5.5.Деление чисел со знаком
Для деления чисел со знаком предназначена команда idiv делитель
Выполняет целочисленное деление со знаком ax или dx:ax (в зависимости от размера источника) на источник (регистр или переменная) и помещает результат в al или ax, а остаток — в ah или dx соответственно. Результат всегда округляется в сторону нуля, знак остатка всегда совпадает со знаком делимого, абсолютное значение остатка всегда меньше абсолютного значения делителя. Значения флагов cf, of, sf, zf, af и pf после этой команды не определены, а переполнение или деление на ноль вызывает исключение #DE (ошибка при делении) в защищенном режиме и прерывание 0 — в реальном.
Для этой команды справедливы все рассмотренные положения, касающиеся команд и чисел со знаком. Отметим лишь особенности возникновения исключения 0, “деление на ноль” в случае чисел со знаком. Оно возникает при выполнении команды idiv по одной из следующих причин:
делитель равен нулю;
частное не входит в отведенную для него разрядную сетку.
45