Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

epd629

.pdf
Скачиваний:
18
Добавлен:
02.05.2015
Размер:
1.05 Mб
Скачать

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

Последнее, в свою очередь, может произойти:

при делении делимого величиной в слово со знаком на делитель величиной в байт со знаком, причем значение делимого более чем в 128 раз больше значения делителя (т.к. частное не должно находиться вне диапазона от –128 до +127);

при делении делимого величиной в двойное слово со знаком на делитель величиной в слово со знаком, причем значение делимого более чем в 32 768 раз больше значения делителя (т.к. частное не должно находиться вне диапазона от –32 768 до +32 767).

Вспомогательные команды для целочисленных операций – рассматриваются ниже.

Контрольные вопросы

1.Общие сведенья об арифметических операциях над числами со знаком.

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

3.Вычитание двоичных чисел со знаком. Применение дополнительного кода. Вычитание положительного числа из отрицательного числа.

4.Умножение чисел со знаком.

5.Деление чисел со знаком. Причины возникновения ситуации «деление на ноль».

46

6. ДОПОЛНИТЕЛЬНЫЕ СВЕДЕНИЯ

6.1. Включаемые файлы

Часто оказывается желательным включить один и тот же блок исходного кода ассемблера в несколько исходных модулей. В этом случае чрезвычайно удобной оказывается директива include.

Когда ассемблер встречает директиву include (включить), текст включаемого файла включается в ассемблирование текущего исходного модуля ассемблера. Например, если файл mainprog.asm содержит:

. . .

.code mov ax,1

include incprog.asm push ax

. . .

афайл incprog.asm содержит: mov bx,5

add ax,bx

то результат ассемблирования файла mainprog.asm будет в точности эквивалентен ассемблированию кода:

. . .

.code mov ax,1 mov bx,5 add ax,bx push ax

. . .

Допускается вложенность включаемых файлов на произвольную глубину, т.е. включаемые файлы также могут содержать директивы include (включать другие файлы). В файле листинга ассемблер слева от каждой включенной строки помещает номер, указывающий глубину вложенности файлов модулей.

6.2. Команды преобразования типов

Что делать, если размеры операндов, участвующих в арифметических операциях, разные? Например, один операнд является словом, а другой занимает двойное слово. Выше сказано, что в операции сложения должны участвовать операнды одного формата.

47

Если числа без знака, то на базе исходного операнда (слова) надо сформировать новый (формата двойного слова), старшие разряды которого заполнить нулями.

Для чисел со знаком используются команды преобразования типа. Команды МП 8086 расширяют байты в слова, слова - в двойные слова. Команды преобразования типа особенно полезны при преобразовании целых со знаком, так как они автоматически заполняют старшие биты вновь формируемого операнда значениями знакового бита старого объекта. Эта операция приводит к целым значениям того же знака и той же величины, что и исходная, но уже в более длинном формате:

cbw (convert byte to word) - команда преобразования байта (в регистре al) в слово (в регистре ax) путем распространения значения старшего бита al на все биты регистра ah;

cwd (convert word to double) - команда преобразования слова (в регистре ax) в двойное слово (в регистрах dx:ax) путем распространения значения старшего бита ax на все биты регистра dx.

Например, сложение байта и слова, если это числа со знаком, может быть реализовано следующим образом:

.data

 

b1 db ?

 

w1 dw ?

 

.code

 

main proc far

 

. . .

 

mov al,b1

;al:=b1

cbw

;ax:=b1

add ax,w1

;ax:=b1+w1

; проверка флага cf. Если cf=1, то ошибка

. . .

Другой пример, сложение двойного слова и слова, если это числа со знаком:

.data

 

d1 dd ?

 

w1 dw ?

 

.code

 

main proc far

 

. . .

 

mov al,w1

;ax:=w1

cwd

;(dx:ax) :=w1

add ax,word ptr d1

 

adc dx,word ptr d1+2

; (dx:ax):=d1+w1

 

48

; проверка флага cf. Если cf=1, то ошибка

. . .

6.3. Отрицание с дополнением до двух

Формат команды: neg <операнд>

Команда выполняет инвертирование значения операнда. Физически команда выполняет вычитание значения операнда из нуля:

<операнд>= 0 – <операнд> Команду neg операнд можно применять:

для смены знака;

для выполнения вычитания из константы.

Т.к. команды sub и sbb не позволяют вычесть что-либо из константы, то данную операцию можно выполнить с помощью двух

команд:

 

neg ax

;смена знака (ax)

. . .

 

add ax,340

;фактически вычитание: ax:=340-ax

6.4.Арифметические и логические операции

вмикропроцессорах МП8086 и МП80386

6.4.1.Арифметические операции в микропроцессорах МП8086 и МП80386 показаны для беззнаковых чисел на примерах вычисления по формулам (приложение 5):

d1+w1+b1+d2+w2+b2

d1-w1-b1-d2-w2-b2

b1*b2*w1*d1

6.4.2.Логические поразрядные операции в микропроцессорах МП8086 и МП80386 показаны на примерах вычисления по формулам (приложение 5):

d1 or w1 or b1 or d2 or w2 or b2

d1 and w1 and b1 and d2 and w2 and b2

Контрольные вопросы

1.Включаемые файлы. Директива include.

2.Команды преобразования типов для знаковых данных (cbw, cwd,

cdq).

3.Отрицание с дополнением до двух (neg).

49

7. ТИПИЧНЫЕ ОШИБКИ ПРИ ВЫПОЛНЕНИИ РАБОТЫ

7.1.Перечень часто встречающихся ошибок

7.1.1.Перечень ошибок тот же, что и в первых двух работах (см.

уч.пос., ч.1 и 2):

- открытая процедура или открытый сегмент; - программист забывает о возврате в DOS;

- вызов процедуры, которая портит содержимое нужных регистров; - неопределенные символические имена; - повторное определение символического имени; - неправильный порядок операндов; - неправильное использование операндов;

- потеря содержимого регистра при умножении; - не подготовлены регистры при делении;

- неправильное использование регистра после деления; - потеря содержимого регистра при делении;

- неправильное использование регистров в командах cbw и cwd;

- применение команд преобразования cbw и cwd к беззнаковым данным;

- изменение отдельными инструкциями флага переноса; - программист долго не использует состояние флагов; - ошибки при использовании регистров; - выход из диапазона адресов;

- программист забывает об инструкции ret; - генерация неверного типа возврата;

- ошибки при использовании условных переходов; - перепутаны операнды в памяти и непосредственные операнды; - границы сегментов.

7.1.2Типичные ошибки, которые допускаются при программировании на ассемблере, и рекомендации, как можно их избежать, приведены ниже. Источники данной информации: [1; 4; 5; 6].

7.2.Открытая процедура или открытый сегмент

Если генерируется предупреждающее сообщение, что сегмент открыт, наиболее вероятной причиной ошибки является:

- неверная запись имени сегмента или процедуры

50

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]