ГЛАВА 12 Арифметические операции I: Обработка двоичных данных

__________

Ц е л ь: Дать сведения об операциях сложения, вычитания, умножения и

деления двоичных данных.

ВВЕДЕНИЕ

________________________________________________________________

Несмотря на то, что мы привыкли к десятичной арифметике (база 10),

компьютер работает только с двоичной арифметикой (база 2). Кроме того,

ввиду ограничения, накладываемого 16-битовыми регистрами, большие величины

требуют специальной обработки.

Данная глава дает сведения об операциях сложения, вычитания,

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

много примеров и предупреждений о различных ловушках для опрометчивых

исследователей мира микропроцессора. В следующей главе будут раскрыты

операции преобразования между двоичными данными и ASCII кодами.

СЛОЖЕНИЕ И ВЫЧИТАНИЕ

________________________________________________________________

Команды ADD и SUB выполняют сложение и вычитание байтов или слов,

содержащих двоичные данные. Вычитание выполняется в компьютере по методу

сложения с двоичным дополнением: для второго операнда устанавливаются

обратные значения бит и прибавляется 1, а затем происходит сложение с

первым операндом. Во всем, кроме первого шага, операции сложения и

вычитания идентичны.

На рис.12.1 представлены примеры команд ADD и SUB, обрабатывающие

байты или слова. В процедуре B10ADD используется команда ADD для сложения

байтов, а в процедуре C10SUB команда SUB вычитает слова. Примеры

показывают все пять возможных ситуаций:

сложение/вычитание регистр-регистр;

сложение/вычитание память-регистр;

сложение/вычитание регистр-память;

сложение/вычитание регистр-непоср.значение;

сложение/вычитание память-непоср.значение.

__________

page 60,132

TITLE EXADD (СОМ) Сложение и вычитание

CODESG SEGMENT PARA 'Code'

ASSUME CS:CODESG,DS:CODESG,SS:CODESG

ORG 100H

BEGIN: JMP SHORT MAIN

; --------------------------------------------

BYTEA DB 64H ;Элементы данных

BYTEB DB 40H

BYTEC DB 16H

WORDA DW 4000H

WORDB DW 2000H

WORDC DW 1000H

; --------------------------------------------

MAIN PROC NEAR ;Основная процедура:

CALL B10ADD ;Вызвать сложение ADD

CALL C10SUB ;Вызвать вычитание SUB

RET

MAIN ENDP

; Пример сложения байт:

; --------------------

B10ADD PROC

MOV AL,BYTEA

MOV BL,BYTEB

ADD AL,BL ;Регистр и регистр

ADD AL,BYTEC ;Память и регистр

ADD BYTEA,BL ;Регистр и память

ADD BL,10H ;Непосредств. и регистр

ADD BYTEA,25H ;Непосредств. и память

RET

B10ADD ENDP

; Пример вычитания слов:

; ---------------------

C10SUB PROC

MOV AX,WORDA

MOV BX,WORDB

SUB AX,BX ;Регистр из регистра

SUB AX,WORDC ;Память из регистра

SUB WORDA,BX ;Регистр из памяти

SUB BX,1000H ;Непосредств. из peг.

SUB WORDA,256H ;Непосредств. из пам.

RET

C10SUB ENDP

CODESG ENDS

END BEGIN

__________

Рис.12.1. Примеры команд ADD и SUB.

Поскольку прямой операции память-память не существует, данная

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

слова WORDB прибавляется содержимое слова WORDA, описанных как DW:

MOV AX,WORDA

ADD AX,WORDB

MOV WORDB,AX

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

--------------

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

содержит знаковый бит и семь бит данных, т.е. значения от -128 до +127.

Результат арифметической операции может легко превзойти емкость

однобайтового регистра. Например, результат сложения в регистре AL,

превышающий его емкость, автоматически не переходит в регистр AH.

Предположим, что регистр AL содержит шест.60, тогда результат команды

ADD AL,20H

генерирует в AL сумму - шест.80. Но операция также устанавливает флаг

переполнения и знаковый флаг в состояние "отрицательно". Причина

заключается в том, что шест.80 или двоичное 1000 0000 является

отрицательным числом. Т.е. в результате, вместо +128, мы получим -128. Так

как регистр AL слишком мал для такой операции и следует воспользоваться

регистром AX. В следующем примере команда CBW (Convert Byte to Word -

преобразовать байт в слово) преобразует шест.60 в регистре AL в шест.0060

в регистре AX, передавая при этом знаковый бит (0) через регистр AH.

Команда ADD генерирует теперь в регистре AX правильный результат:

шест.0080, или +128:

CBW ;Расширение AL до AX

ADD AX,20H ;Прибавить к AX

Но полное слово имеет также ограничение: один знаковый бит и 15 бит

данных, что соответствует значениям от -32768 до +32767. Рассмотрим далее

как можно обрабатывать числа, превышающие эти пределы.

Многословное сложение

-----------------------

Максимальное возможное значение в регистре +32767 ограничивает

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

два способа выполнения арифметических операций. Первый способ - более

прост, но специфичен, второй - сложнее, но имеет общий характер.

__________

page 60,132

TITLE EXDBADD (COM) Пример сложения двойных слов

CODESG SEGMENT PARA 'Code'

ASSUME CS:CODESG,DS:CODESG,SS:CODESG

ORG 100H

BEGIN: JMP SHORT MAIN

; -------------------------------------------

WORD1A DW 0123H ;Элементы данных

WORD1B DW 0BC62H

WORD2A DW 0012H

WORD2B DW 553AH

WORD3A DW ?

WORD3B DW ?

; -------------------------------------------

MAIN PROC NEAR ;0сновная процедура:

CALL D10DWD ;Вызвать сложение 1

CALL E10DWD ;Вызвать сложение 2

RET

MAIN ENDP

; Пример сложения двойных слов:

; ----------------------------

D10DWD PROC

MOV AX,WORD1B ;Сложить правые слова

ADD AX,WORD2B

MOV WORD3B,AX

MOV AX,WORD1A ;Сложить левые слова

ADC AX,WORD2A ; с переносом

MOV WORD3A,AX

RET

D10DWD ENDP

; Сложение чисел любой длины:

; --------------------------

E10DWD PROC

CLC ;Очистить флаг переноса

MOV CX,2 ;Установить счетчик

LEA SI,WORD1B ;Левое слово DWORD1

LEA DI,WORD2B ;Левое слово DWORD2

LEA BX,WORD3B ;Левое слово суммы

Е20:

MOV AX,[SI] ;Поместить слово в AX

ADC AX,[DI] ;Сложить с переносом

MOV [BX],AX ;Сохранить слово

DEC SI

DEC SI

DEC DI

DEC DI

DEC BX

DEC BX

LOOP Е20 ;Повторить цикл

RET

E10DWD ENDP

CODESG ENDS

END BEGIN

__________

Рис.12.2. Сложение двойных слов.

На рис.12.2 процедура D10DWD демонстрирует простой способ сложения

содержимого одной пары слов (WORD1A и WORD1B) с содержимым второй пары

слов (WORD2A и WORD2B) и сохранения суммы в третьей паре слов (WORD3A и

WORD3B). Сначала выполняется сложение правых слов:

WORD1B BC62

WORD2B 553A

-----

Сумма: 1119C

Сумма - шест.1119C превышает емкость регистра AX. Переполнение

вызывает установку флага переноса в 1. Затем выполняется сложение левых

слов, но в данном случае, вместо команды ADD используется команда сложения

с переносом ADC (ADd with Carry). Эта команда складывает два значения, и

если флаг CF уже установлен, то к сумме прибавляется 1:

WORD1A 0123

WORD2A 0012

Плюс перенос 1

----

Сумма: 0136

При использовании отладчика DEBUG для трассировки арифметических

команд можно увидеть эту сумму 0136 в регистре AX, и обpатные значения

3601 в поле WORD3A и 9C11 в поле WORD3B.

На рис.12.2 процедура E10DWD демонстрирует подход к сложению значений

любой длины. Действие начинается со сложения самых правых слов

складываемых полей. В первом цикле складываются правые cлова, во втором -

слова, расположенные левее. При этом адреса в регистрах SI, DI и BX

уменьшаются на 2. По две команда DEC выполняют эту операцию для каждого

регистра. Применять команду

SUB reg,02

в данном случае нельзя, т.к. при этом будет очищен флаг переноса, что

приведет к искажению результата сложения.

Ввиду наличия цикла, используется только одна команда сложения ADC.

Перед циклом команда CLC (CLear Carry - очистить флаг переноса)

устанавливает нулевое значение флага переноса. Для работы данного метода

необходимо: 1) обеспечить смежность слов, 2) выполнять обработку справа

налево и 3) загрузить в регистр CX число складываемых слов.

Для многословного вычитания используется команда SBB (SuBtract with

Borrow - вычитание с заемом) эквивалентная команде ADC. Заменив в

процедуре E10DWD (рис.12.2) команду ADC на SBB, получим процедуру для

вычитания.

БЕЗЗНАКОВЫЕ И ЗНАКОВЫЕ ДАННЫЕ

________________________________________________________________

Многие числовые поля не имеют знака, например, номер абонента, aдрес

памяти. Некоторые числовые поля предлагаются всегда положительные,

например, норма выплаты, день недели, значение числа ПИ. Другие числовые

поля являются знаковые, так как их содержимое может быть положительным или

отрицательным. Например, долговой баланс покупателя, который может быть

отрицательным при переплатах, или алгебраическое число.

Для беззнаковых величин все биты являются битами данных и вместо

ограничения +32767 регистр может содержать числа до +65535. Для знаковых

величин левый байт является знаковым битом. Команды ADD и SUB не делают

разницы между знаковыми и беззнаковыми величинами, они просто складывают и

вычитают биты. В следующем примере сложения двух двоичных чисел, первое

число содержит единичный левый бит. Для беззнакового числа биты

представляют положительное число 249, для знакового - отрицательное число

-7:

Беззнаковое Знаковое

11111001 249 -7

00000010 2 +2

--- --

11111011 251 -5

Двоичное представление результата сложения одинаково для беззнакового и

знакового числа. Однако, биты представляют +251 для беззнакового числа и

-5 для знакового. Таким образом, числовое содержимое поля может

интерпретироваться по разному.

Состояние "перенос" возникает в том случае, когда имеется пеpенос в

знаковый разряд. Состояние "переполнение" возникает в том случае, когда

перенос в знаковый разряд не создает переноса из разрядной сетки или

перенос из разрядной сетки происходит без переноса в знаковый разряд. При

возникновении переноса при сложении беззнаковых чисел, результат

получается неправильный:

Беззнаковое Знаковое CF OF

11111100 252 -4

00000101 5 +5

--- --

00000001 1 1 1 0

(неправильно)

При возникновении переполнения при сложении знаковых чисел, результат

получается неправильный:

Беззнаковое Знаковое CF OF

01111001 121 +121

00001011 11 +11

--- ----

10000100 132 -124 0 1

(неправильно)

При операциях сложения и вычитания может одновременно возникнуть и

переполнение, и перенос:

Беззнаковое Знаковое CF OF

11110110 246 -10

10001001 137 -119

--- ----

01111111 127 +127 1 1

(неправильно) (неправильно)

УМНОЖЕНИЕ

________________________________________________________________

Операция умножения для беззнаковых данных выполняется командой MUL, а

для знаковых - IMUL (Integer MULtiplication - умножение целых чисел).

Ответственность за контроль над форматом обрабатываемых чисел и за выбор

подходящей команды умножения лежит на самом программисте. Существуют две

основные операции умножения:

"Б а й т н а б а й т". Множимое находится в регистре AL, а

множитель в байте памяти или в однобайтовом регистре. После умножения

произведение находится в регистре AX. Операция игнорирует и стиpает любые

данные, которые находились в регистре AH.

| AH | AL | | AX |

До умножения: | |Множимое| После: |Произведение|

"С л о в о н а с л о в о". Множимое находится в регистре AX, а

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

находится в двойном слове, для которого требуется два регистра: старшая

(левая) часть произведения находится в регистре DX, а младшая (правая)

часть в регистре AX. Операция игнорирует и стирает любые данные, которые

находились в регистре DX.

| AX | | DX || AX |

До умножения:|Множимое| После: |Ст.часть||Мл.часть|

| Произведение |

В единственном операнде команд MUL и IMUL указывается множитель.

Рассмотрим следующую команду:

MUL MULTR

Если поле MULTR определено как байт (DB), то операция предполагает

умножение содержимого AL на значение байта из поля MULTR. Если поле MULTR

определено как слово (DW), то операция предполагает умножение содержимого

AX на значение слова из поля MULTR. Если множитель находится в регистре,

то длина регистра определяет тип операции, как это показано ниже:

MUL CL ;Байт-множитель: множимое в AL, произвед. в AX

MUL BX ;Слово-множитель:множимое в AX, произвед. в DX:AX

Беззнаковое умножение: Команда MUL

------------------------------------

Команда MUL (MULtiplication - умножение) умножает беззнаковые числа.

На рис.12.3 в процедуре C10MUL дано три примера умножения: байт на байт,

слово на слово и слово на байт. Первый пример команды MUL умножает шест.80

(128) на шест.47 (64). Произведение шест.2000 (8192) получается в регистре

AX.

__________

page 60,132

TITLE EXMULT (COM) Пример команд умножения

CODESG SEGMENT PARA 'Code'

ASSUME CS:CODESG,DS:CODESG,SS:CODESG

OR6 100H

BEGIN: JMP SHORT MAIN

; -------------------------------------------

BYTE1 DB 80H

BYTE2 DB 40H

WORD1 DW 8000H

WORD2 DW 4000H

; -------------------------------------------

MAIN PROC NEAR ;Основная процедура:

CALL C10MUL ;Вызвать умнож. MUL

CALL D10IMUL ;Вызвать умнож. IMUL

RET

MAIN ENDP

; Пример умножения MUL:

; --------------------

C10MUL PROC

MOV AL,BYTE1 ;Байт * байт

MUL BYTE2 ; произведение в AХ

MOV AX,WORD1 ;Слово * слово

MUL WORD2 ; произведение в DX:AX

MOV AL,BYTE1 ;Байт * слово

SUB AН,AН ; расшир. множ. в AН

MUL WORD1 ; произведение в DX:AX

RET

C10MUL ENDP

; Пример умножения IMUL:

; ---------------------

D10IMUL PROC

MOV AL,BYTE1 ;Байт * байт

IMUL BYTE2 ; произведение в AХ

MOV AX,WORD1 ;Слово * слово

IMUL WORD2 ; произвед. в DX:AX

MOV AL,BYTE1 ;Байт * слово

CBW ; расшир. множ. в AН

IMUL WORD1 ; произвед. в DX:AX

RET

D10IMUL ENDP

CODESG ENDS

END BEGIN

__________

Рис.12.3. Беззнаковое и знаковое умножение.

Второй пример команды MUL генерирует шест.10000000 в регистpах DX:AX.

Третий пример команды MUL выполняет умножение слова на байт и требует

расширение байта BYTE1 до размеров слова. Так как предполагаются

беззнаковые величины, то в примере левый бит регистра AH равен нулю. (При

использовании команды CBW значение левого бита регистpа AL может быть 0

или 1). Произведение - шест.00400000 получается в регистрах DX:AX.

Знаковое умножение: Команда IMUL

----------------------------------

Команда IMUL (Integer MULtiplication - умножение целых чисел)

умножает знаковые числа. На рис.12.3 в процедуре D10IMUL используются те

же три примера умножения, что и в процедуре C10MUL, но вместо команд MUL

записаны команды IMUL.

Первый пример команды IMUL умножает шест.80 (отрицательное число) на

шест.40 (положительное число). Произведение - шест.E000 получается в

регистре AX. Используя те же данные, команда MUL дает в результате

шест.2000, так что можно видеть разницу в использовании команд MUL и IMUL.

Команда MUL рассматривает шест.80 как +128, а команда IMUL - как -128. В

результате умножения -128 на +64 получается -8192 или шест.E000.

(Попробуйте преобразовать шест.Е000 в десятичный формат).

Второй пример команды IMUL умножает шест.8000 (отрицательное

значение) на шест.2000 (положительное значение). Произведение -

шест.F0000000 получается в регистрах DX:AX и представляет собой

oтрицательное значение.

Третий пример команды IMUL перед умножением выполняет расширение

байта BYTE1 до размеров слова в регистре AX. Так как значения

предполагаются знаковые, то в примере используется команда CBW для

перевода левого знакового бита в регистр AH: шест.80 в pегистре AL

превращается в шест.FF80 в регистре AX. Поскольку множитель в слове WORD1

имеет также отрицательное значение, то произведение должно получится

положительное. В самом деле: шест.00400000 в регистрах DX:AX - такой же

результат, как и в случае умножения командой MUL, которая предполагала

положительные сомножители.

Таким образом, если множимое и множитель имеет одинаковый знаковый

бит, то команды MUL и IMUL генерируют одинаковый результат. Но, если

сомножители имеют разные знаковые биты, то команда MUL вырабатывает

положительный результат умножения, а команда IMUL - отрицательный.

Можно обнаружить это, используя отладчик DEBUG для трассировки

примеров.

П о в ы ш е н и е э ф ф е к т и в н о с т и у м н о ж е н и я: При

умножении на степень числа 2 (2,4,8 и т.д.) более эффективным является

сдвиг влево на требуемое число битов. Сдвиг более чем на 1 требует

загрузки величины сдвига в регистр CL. В следующих примерах предположим,

что множимое находится в регистре AL или AX:

Умножение на 2: SHL AL,1

Умножение на 8: MOV CL,3

SHL AX,CL

Многословное умножение

------------------------

Обычно умножение имеет два типа: "байт на байт" и "слово на слово".

Как уже было показано, максимальное знаковое значение в слове ограничено

величиной +32767. Умножение больших чисел требует выполнения некоторых

дополнительных действий. Рассматриваемый подход предполагает умножение

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

следующее умножение в десятичном формате:

х12

-----

-----

Представим, что десятичная арифметика может умножать только двузначные

числа. Тогда можно умножить 13 и 65 на 12 раздельно, cледующим образом:

13 65

х12 х12

--- ---

26 130

13 65

--- ---

156 780

Следующим шагом сложим полученные произведения, но поскольку число 13

представляло сотни, то первое произведение в действительности будет 15600:

+780

-----

Ассемблерная программа использует аналогичную технику за исключением

того, что данные имеют размерность слов (четыре цифры) в шестнадцатеричном

формате.

У м н о ж е н и е д в о й н о г о с л о в а н а с л о в о.

Процедура E10XMUL на рис.12.4 умножает двойное слово на слово. Множимое,

MULTCND, состоит из двух слов, содержащих соответственно шест.3206 и

шест.2521. Определение данных в виде двух слов (DW) вместо двойного слова

(DD) обусловлено необходимостью правильной адресации для команд MOV,

пересылающих слова в регистр AX. Множитель MULTPLR содержит шест.6400.

Область для записи произведения, PRODUCT, состоит из трех слов. Первая

команда MUL перемножает MULTPLR и правое cлово поля MULTCND; произведение

- шест.0E80 E400 записывается в PRODUCT+2 и PRODUCT+4. Вторая команда MUL

перемножает MULTPLR и левое слово поля MULTCND, получая в результате шест.

138A 5800. Далее выполняется сложение двух произведений следующим образом:

Произведение 1: 0000 0E80 E400

Произведение 2: 138A 5800

--------------

Результат: 138A 6680 E400

Так как первая команда ADD может выработать перенос, то второе

cложение выполняется командой сложения с переносом ADC (ADd with Carry). В

силу обратного представления байтов в словах в процессоpах 8086/8088,

область PRODUCT в действительности будет содержать значение 8A13 8066

00E4. Программа предполагает, что первое слово в области PRODUCT имеет

начальное значение 0000.

__________

TITLE EXDWMUL - Умножение двойных слов

CODESG SEGMENT PARA 'Code'

ASSUME CS:CODESG,DS:CODESG,SS:CODESG

ORG 100H

BEGIN: JMP SHORT MAIN

; ---------------------------------------------

MULTCND DW 3206H ;Элементы данных

DW 2521H

MULTPLR DW 6400H

DW 0A26H

PRODUCT DW 0

DW 0

DW 0

DW 0

; ---------------------------------------------

MAIN PROC NEAR ;Основная процедура

CALL E10XMUL ;Вызвать 1-е умножение

CALL Z10ZERO ;Очистить произведение

CALL F10XMUL ;Вызвать 2-е умножение

RET

MAIN ENDP

; Умножение двойного слова на слово:

; -----------------------------------------------

E10XMUL PROC

MOV AX,MULTCND+2 ;Умножить правое слова

MUL MULTPLR ; множимого

MOV PRODUCT+4,AX ;Записать произведение

MOV PRODUCT+2,DX

MOV AX,MULTCND ;Умножить левое слово

MUL MULTPLR ; множимого

ADD PRODUCT+2,AX ;Сложить с полученным ранее

ADC PRODUCT,DX

RET

E10XMUL ENDP

; Перемножение двух двойных слов:

; --------------------------------------------

F10XMUL PROC

MOV AX,MULTCND+2 ;Слово-2 множимого

MUL MULTPLR+2 ; * слово-2 множителя

MOV PRODUCT+6,AX ;Сохранить результат

MOV PRODUCT+4,DX

MOV AX,MULTCND+2 ;Слово-2 множимого

MUL MULTPLR ; * слово-1 множителя

ADD PRODUCT+4,AX ;Сложить с предыдущим

ADC PRODUCT+6,DX

ADC PRODUCT,00 ;Прибавить перенос

MOV AX,MULTCND ;Слово-1 множимого

MUL MULTPLR+2 ; * слово-2 множителя

ADD PRODUCT+4,AX ;Сложить с предыдущим

ADC PRODUCT+6,DX

ADC PRODUCT,00 ;Прибавить перенос

MOV AX,MULTCND ;Слово-1 множимого

MUL MULTPLR ; * слово-1 множителя

ADD PRODUCT+2,AX ;Сложить с предыдущим

ADC PRODUCT,DX

RET

F10XMUL ENDP

; Очистка области результата:

; ----------------------------------------

Z10XMUL PROC

MOV PRODUCT,0000

MOV PRODUCT+2,0000

MOV PRODUCT+4,0000

MOV PRODUCT+6,0000

RET

Z10XMUL ENDP

CODESG ENDS

END BEGIN

__________

Рис.12.4. Многословное умножение.

У м н о ж е н и е "д в о й н о г о с л о в а н а д в о й н о е

с л о в о". Умножение двух двойных слов включает следующие четыре операции

умножения:

Множимое Множитель

слово 2 х слово 2

слово 2 х слово 1

слово 1 х слово 2

слово 1 х слово 1

Каждое произведение в регистрах DX и AX складывается с соответствующим

словом в окончательном результате. Пример такого умножения приведен в

процедуре F10XMUL на рис.12.4. Множимое MULTCND содержит шест.3206 2521,

множитель MULTPLR - шест.6400 0A26. Результат заносится в область PRODUCT,

состоящую из четырех слов.

Хотя логика умножения двойных слов аналогична умножению двойного

слова на слово, имеется одна особенность, после пары команд сложения

ADD/ADC используется еще одна команда ADC, которая прибавляет 0 к значению

в поле PRODUCT. Это необходимо потому, что первая команда ADC сама может

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

команда ADC прибавит 0, если переноса нет, и прибавит 1, если перенос

есть. Финальная пара команд ADD/ADC не требует дополнительной команды ADC,

так как область PRODUCT достаточно велика для генерации окончательного

результата и переноса на последнем этапе не будет.

Окончательный результат 138A 687C 8E5C CCE6 получится в поле PRODUCT

в обратной записи байт в словах. Выполните трассировку этого примера с

помощью отладчика DEBUG.

СДВИГ РЕГИСТРОВОЙ ПАРЫ DX:AX

________________________________________________________________

Следующая подпрограмма может быть полезна для сдвига содержимого

pегистровой пары DX:AX вправо или влево. Можно придумать более эффективный

метод, но данный пример представляет общий подход для любого числа циклов

(и, соответственно, сдвигов) в регистре CX. Заметьте, что сдвиг единичного

бита за разрядную сетку устанавливает флаг переноса.

Сдвиг влево на 4 бита

MOV CX,04 ;Инициализация на 4 цикла

C20: SHL DX,1 ;Сдвинуть DX на 1 бит влево

SHL AX,1 ;Сдвинуть AX на 1 бит влево

ADC DX,00 ;Прибавить значение переноса

LOOP C20 ;Повторить

Сдвиг вправо на 4 бита

MOV CX,04 ;Инициализация на 4 цикла

D20: SHR AX,1 ;Сдвинуть AX на 1 бит вправо

SHR DX,1 ;Сдвинуть DX на 1 бит вправо

JNC D30 ;Если есть перенос,

OR AH,10000000B ; то вставить 1 в AH

D30: LOOP D20 ;Повторить

Ниже приведен более эффективный способ для сдвига влево, не требующий

организации цикла. В этом примере фактор сдвига записывается в регистр CL.

Пример написан для сдвига на 4 бита, но может быть адаптирован для других

величин сдвигов:

MOV CL,04 ;Установить фактор сдвига

SHL DX,CL ;Сдвинуть DX влево на 4 бита

MOV BL,AH ;Сохранить AH в BL

SHL AX,CL ;Сдвинуть AX влево на 4 бита

SHL BL,CL ;Сдвинуть BL вправо на 4 бита

OR DL,BL ;Записать 4 бита из BL в DL

ДЕЛЕНИЕ

________________________________________________________________

Операция деления для беззнаковых данных выполняется командой DIV, a

для знаковых - IDIV. Ответственность за подбор подходящей команды лежит на

программисте. Существуют две основные операции деления:

Д е л е н и е "с л о в а н а б а й т". Делимое находится в регистре

AX, а делитель - в байте памяти или а однобайтовом регистре. После деления

остаток получается в регистре AH, а частное - в AL. Так как однобайтовое

частное очень мало (максимально +255 (шест.FF) для беззнакового деления и

+127 (шест.7F) для знакового), то данная операция имеет ограниченное

использование.

| AX | | AH | AL |

До деления: |Делимое| После: |Остаток|Частное|

Д е л е н и е "д в о й н о г о с л о в а н а с л о в о". Делимое

находится в регистровой паре DX:AX, а делитель - в слове памяти или а

регистре. После деления остаток получается в регистре DX, а частное в

регистре AX. Частное в одном слове допускает максимальное значение +32767

(шест.FFFF) для беззнакового деления и +16383 (шест.7FFF) для знакового.

| DX || AX | | AH || AL |

До деления: |Ст.часть||Мл.часть| После: |Остаток||Частное|

| Делимое |

В единственном операнде команд DIV и IDIV указывается делитель. Рассмотрим

следующую команду:

DIV DIVISOR

Если поле DIVISOR определено как байт (DB), то операция предполагает

деление слова на байт. Если поле DIVISOR определено как слово (DW), то

операция предполагает деление двойного слова на слово.

При делении, например, 13 на 3, получается результат 4 1/3. Частное

есть 4, а остаток - 1. Заметим, что ручной калькулятор (или программа на

языке BASIC) выдает в этом случае результат 4,333.... Значение содержит

целую часть (4) и дробную часть (,333). Значение 1/3 и 333... есть дробные

части, в то время как 1 есть остаток от деления.

Беззнаковое деление: Команда DIV

----------------------------------

Команда DIV делит беззнаковые числа. На рис.12.5 в процедуре D10DIV

дано четыре примера деления: слово на байт, байт на байт, двойное слово на

слово и слово на слово. Первый пример команды DIV делит шест.2000 (8092)

на шест.80 (128). В результате остаток 00 получается в регистре AH, а

частное шест.40 (64) - в регистре AL.

Второй пример команды DIV выполняет прежде расширение байта BYTE1 до

размеров слова. Так как здесь предполагается беззнаковая величина, то в

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

шест.12 получается в регистре AH, а частное шест.05 - в регистре AL.

Третий пример команды DIV генерирует остаток шест.1000 в регистре DX

и частное шест.0080 в регистре AX.

В четвертом примере команды DIV сначала выполняется расширение слова

WORD1 до двойного слова в регистре DX. После деления остаток шест.0000

получится в регистре DX, а частное шест.0002 - в регистре AX.

__________

page 60,132

TITLE EXDIV (COM) Пример операций DIV и IDIV

CODESG SEGMENT PARA 'Code'

ORG 100H

BEGIN: JMP SHORT MAIN

; ---------------------------------------------

BYTE1 DB 80H ;Data items

BYTE2 DB 16H

WORD1 DW 2000H

WORD2 DW 0010H

WORD3 DW 1000H

; ---------------------------------------------

MAIN PROC NEAR ;Основная процедура

CALL D10DIV ;Вызов подпрограммы DIV

CALL E10IDIV ;Вызов подпрограммы IDIV

MAIN ENDP

; Примеры с командой DIV:

; ---------------------------------------------

D10DIV PROC

MOV AX,WORD1 ;Слово / байт

DIV BYTE1 ; остаток:частное в AH:AL

MOV AL,BYTE1 ;Байт / байт

SUB AH,AH ; расширить делимое в AH

DIV BYTE3 ; остаток:частное в AH:AL

MOV DX,WORD2 ;Двойное слово / слово

MOV AX,WORD3 ; делимое в DX:AX

DIV WORD1 ; остаток:частное в DX:AX

MOV AX,WORD1 ;Слово / слово

SUB DX,DX ; расширить делимое в DX

DIV WORD3 ; остаток:частное в DX:AX

RET

D10DIV ENDP

; Примеры с командой IDIV:

; ---------------------------------------------

E10IDIV PROC

MOV AX,WORD1 ;Слово / байт

IDIV BYTE1 ; остаток:частное в AH:AL

MOV AL,BYTE1 ;Байт / байт

CBW ; расширить делимое в AH

IDIV BYTE3 ; остаток:частное в AH:AL

MOV DX,WORD2 ;Двойное слово / слово

MOV AX,WORD3 ; делимое в DX:AX

IDIV WORD1 ; остаток:частное в DX:AX

MOV AX,WORD1 ;Слово / слово

CWD ; расширить делимое в DX

IDIV WORD3 ; остаток:частное в DX:AX

RET

E10DIV ENDP

CODESG ENDS

END BEGIN

__________

Рис.12.5. Беззнаковое и знаковое деление.

Знаковое деление: Команда IDIV

--------------------------------

Команда IDIV (Integer DIVide) выполняет деление знаковых чисел. На

рис.12.5 в процедуре E10IDIV используются те же четыре примера деления,

что и в процедуре D10DIV, но вместо команд DIV записаны команды IDIV.

Первый пример команды IDIV делит шест.2000 (положительное число) на

шест.80 (отрицательное число). Остаток от деления - шест. 00 получается в

регистре AH , а частное - шест. C0 (-64) - в регистре AL. Команда DIV,

используя те же числа, генерирует частное +64.

Шестнадцатиричные результаты трех остальных примеров деления

приведены ниже:

Пример команды IDIV Остаток Частное

2 EE (-18) FB (-5)

3 1000 (4096) 0080 (128)

4 0000 0002

Только в примере 4 вырабатывается такой же результат, что и для команды

DIV. Таким образом, если делимое и делитель имеют одинаковый знаковый бит,

то команды DIV и IDIV генерируют одинаковый pезультат. Но, если делимое и

делитель имеют разные знаковые биты, то команда DIV генерирует

положительное частное, а команда IDIV - отрицательное частное. Можно

обнаружить это, используя отладчик DEBUG для трассировки этих примеров.

Повышение производительности. При делении на степень числа 2 (2, 4, и

т.д.) более эффективным является сдвиг вправо на требуемое число битов. В

следующих примерах предположим, что делимое находится в регистре AX:

Деление на 2: SHR AX,1

Деление на 8: MOV CL,3

SHR AX,CL

Переполнения и прерывания

---------------------------

Используя команды DIV и особенно IDIV, очень просто вызвать

пеpеполнение. Прерывания приводят (по крайней мара в системе, используемой

при тестировании этих программ) к непредсказуемым результатам. В операциях

деления предполагается, что частное значительно меньше, чем делимое.

Деление на ноль всегда вызывает прерывание. Но деление на 1 генерирует

частное, которое равно делимому, что может также легко вызвать прерывание.

Рекомендуется использовать следующее правило: если делитель - байт,

то его значение должно быть меньше, чем левый байт (AH) делителя: если

делитель - слово, то его значение должно быть меньше, чем левое слово (DX)

делителя. Проиллюстрируем данное правило для делителя, равного 1:

Операция деления: Делимое Делитель Частное

Слово на байт: 0123 01 (1)23

Двойное слово на слово: 0001 4026 0001 (1)4026

В обоих случаях частное превышает возможный размер. Для того чтобы

избежать подобных ситуаций, полезно вставлять перед командами DIV и IDIV

соответствующую проверку. В первом из следующих примеpов предположим, что

DIVBYTE - однобайтовый делитель, а делимое находится уже в регистре AX. Во

втором примере предположим, что DIVWORD - двухбайтовый делитель, а делимое

находится в регистровой паре DX:AX.

Слово на байт Двойное слово на байт

CMP AH,DIVBYTE CMP DX,DIVWORD

JNB переполнение JNB переполнение

DIV DIVBYTE DIV DIVWORD

Для команды IDIV данная логика должна учитывать тот факт, что либо

делимое, либо делитель могут быть отрицательными, а так как сравниваются

абсолютные значения, то необходимо использовать команду NEG для временного

перевода отрицательного значения в положительное.

Деление вычитанием

--------------------

Если частное слишком велико, то деление можно выполнить с помощью

циклического вычитания. Метод заключается в том, что делитель вычитается

из делимого и в этом же цикле частное увеличивается на 1. Вычитание

продолжается, пока делимое остается больше делителя. В cледующем примере,

делитель находится в регистре AX, а делимое - в BX, частное вырабатывается

в CX:

SUB CX,CX ;Очистка частного

C20: CMP AX,BX ;Если делимое < делителя,

JB C30 ; то выйти

SUB AX,BX ;Вычитание делителя из делимого

INC CX ;Инкремент частного

JMP C20 ;Повторить цикл

С30: RET ;Частное в CX, остаток в AX

В конце подпрограммы регистр CX будет содержать частное, а AX -

oстаток. Пример умышленно примитивен для демонстрации данной техники

деления. Если частное получается в регистровой паре DX:AX, то необходимо

сделать два дополнения:

1. В метке C20 сравнивать AX и BX только при нулевом DX.

2. После команды SUB вставить команду SBB DX,00.

П р и м е ч а н и е: очень большое частное и малый делитель могут

вызвать тысячи циклов.

ПРЕОБРАЗОВАНИЕ ЗНАКА

________________________________________________________________

Команда NEG обеспечивает преобразование знака двоичных чисел из

положительного в отрицательное и наоборот. Практически команда NEG

устанавливает противоположные значения битов и прибавляет 1. Примеры:

NEG AX

NEG BL

NEG BINAMT ;(байт или слово в памяти)

Преобразование знака для 35-битового (или большего) числа включает

больше шагов. Предположим, что регистровая пара DX:AX содержит 32-битовое

двоичное число. Так как команда NEG не может обрабатывать два регистра

одновременно, то ее использование приведет к неправильному результату. В

следующем примере показано использование команды NOT:

NOT DX ;Инвертирование битов

NOT AX ;Инвертирование битов

ADD AX,1 ;Прибавление 1 к AX

ADC DX,0 ;Прибавление переноса к DX

Остается одна незначительная проблема: над числами, представленными в

двоичном формате, удобно выполнять арифметические операции, если сами

числа определены в программе. Данные, вводимые в программу с дискового

файла, могут также иметь двоичный формат. Но данные, вводимые с

клавиатуры, представлены в ASCII-формате. Хотя ASCII-коды удобны для

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

формат для арифметических вычислений. Но это уже тема следующей главы.

ПРОЦЕССОРЫ INTEL 8087 И 80287 ДЛЯ ОБРАБОТКИ ЧИСЛОВЫХ ДАННЫХ

________________________________________________________________

Системная плата компьютера содержит пустое гнездо, зарезервированное

для числового процессора Intel 8087 (или 80287). Сопроцессор 8087

действует совместно с 8088, а сопроцессор 80287 действует совместно с

80286. Каждый сопроцессор имеет собственный набор команд и средства для

операций с плавающей запятой для выполнения экспоненциальных,

логарифмических и тригонометрических функций. Сопроцессор содержит восемь

80-битовых регистров с плавающей запятой, которые могут представить

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

сопроцессоре выполняются примерно в 100 раз быстрее, чем в основном

процессоре.

Основной процессор выполняет специальные операции и передает числовые

данные в сопроцессор, который выполняет необходимые вычисления и

возвращает результат. Для ассемблирования с помощью транслятора MASM,

необходимо добавлять параметр /E или /R, например, MASM /R.

ОСНОВНЫЕ ПОЛОЖЕНИЯ НА ПАМЯТЬ

________________________________________________________________

- Будьте особенно внимательны при использовании однобайтовых

pегистров. Знаковые значения здесь могут быть от -128 до +127.

- Для многословного сложения используйте команду ADC для учета

переносов от предыдущих сложений. Если операция выполняется в цикле, то

используя команду CLC, установите флаг переноса в 0.

- Используйте команды MUL или DIV для беззнаковых данных и команды

IMUL или IDIV для знаковых.

- При делении будьте осторожны с переполнениями. Если нулевой

делитель возможен, то обеспечьте проверку этой операции. Кроме того,

делитель должен быть больше содержимого регистра AH (для байта) или DX

(для слова).

- Для умножения или деления на степень двойки используйте cдвиг.

Сдвиг вправо выполняется командой SHR для беззнаковых полей и командой SAR

для знаковых полей. Для сдвига влево используются идентичные команды SHL и

SAL.

- Будьте внимательны при ассемблировании по умолчанию. Например, если

поле FACTOR определено как байт (DB), то команда MUL FACTOR полагает

множимое в регистре AL, а команда DIV FACTOR полагает делимое в регистре

AX. Если FACTOR определен как слово (DW), то команда MUL FACTOR полагает

множимое в регистре AX, а команда DIV FACTOR полагает делимое в

регистровой паре DX:AX.

ВОПРОСЫ ДЛЯ САМОПРОВЕРКИ

________________________________________________________________

Все вопросы имеют отношение к следующим данным:

DATAX DW 0148H

DW 2316H

DATAY DW 0237H

DW 4052H

12.1. Закодируйте команды для сложения а) слова DATAX со словом

DATAY; б) двойного слова, начинающегося по адресу DATAX, с двойным словом

в DATAY.

12.2. Объясните действие следующих команд:

STC

MOV BX,DATAX

ADC BX,DATAY

12.3. Закодируйте команды для умножения (MUL): а) слова DATAX на

слово DATAY; б) двойного слова , начинающегося по адресу DATAX, на слово

DATAY.

12.4. Какой делитель, кроме нуля, вызывает ошибку переполнения?

12.5. Закодируйте команды для деления (DIV): а) слова DATAX на 23; б)

двойного слова, начинающегося по адресу DATAX, на слово DATAY.

12.6. Последний пример в разделе "Сдвиг регистровой пары DX:AX"

является более эффективным по сравнению с предыдущими примерами для сдвига

влево на четыре бита. Измените пример для сдвига вправо на четыре бита.

ГЛАВА 13 Арифметические операции II:

Обработка данных в форматах ASCII и BCD

__________

Ц е л ь: Рассмотреть ASCII и BCD форматы данных и дать сведения о

преобразованиях между этими форматами и двоичным форматом.

ВВЕДЕНИЕ

________________________________________________________________

Для получения высокой производительности компьютер выполняет

aрифметические операции над числами в двоичном формате. Как показано в

гл.12, этот формат не вызывает особых трудностей, если данные определены в

самой программе. Во многих случаях новые данные вводятся программой с

клавиатуры в виде ASCII символов в деcятичном формате. Аналогично вывод

информации на экран осуществляется в кодах ASCII. Например, число 23 в

двоичном представлении выглядит как 00010111 или шест.17; в коде ASCII на

каждый cимвол требуется один байт и число 25 в ASCII-коде имеет внутpеннее

представление шест.3235.

Назначение данной главы - показать технику преобразования данных из

ASCII-формата в двоичный формат для выполнения арифметических операций и

обратного преобразования двоичных результатов в ASCII-формат для вывода на

экран или принтер. Программа, приведенная в конце главы , демонстрирует

большую часть матеpиала гл.1 - 12.

При программировании на языках высокого уровня, таких как BASIC или

Pascal, для обозначения порядка числа или положения десятичной запятой

(точки) можно положиться на компилятор. Однако, компьютер не распознает

десятичную запятую (точку) в арифметических полях. Так как двоичные числа

не имеют возможности установки десятичной (или двоичной) запятой (точки),

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

обрабатываемых чисел.

ASCII-ФОРМАТ

________________________________________________________________

Данные, вводимые с клавиатуры, имеют ASCII-формат, например, буквы

SAM имеют в памяти шестнадцатиричное представление 53414D, цифры 1234 -

шест.31323334. Во многих случаях формат алфавитных данных, например, имя

человека или описание статьи, не меняется в программе. Но для выполнения

арифметических операций над числовыми значениями, такими как

шест.31323334, требуется специальная обработка.

С помощью следующих ассемблерных команд можно выполнять

арифметические операции непосредственно над числами в ASCII-формате:

AAA (ASCII Adjust for Addition - коррекция для сложения ASCII-кода)

AAD (ASCII Adjust for Division - коррекция для деления ASCII-кода)

AAM (ASCII Adjust for Multiplication - коррекция для умножения ASCII-кода)

AAS (ASCII Adjust for Subtraction - коррекция для вычитания ASCII-кода)

Эти команды кодируются без операндов и выполняют автоматическую коррекцию

в регистре AX. Коррекция необходима, так как ASCII-код представляет так

называемый распакованный десятичный формат, в то время, как компьютер

выполняет арифметические операции в двоичном формате.

Сложение в ASCII-формате

--------------------------

Рассмотрим процесс сложения чисел 8 и 4 в ASCII-формате:

Шест. 38

--

Шест. 6C

Полученная сумма неправильна ни для ASCII-формата, ни для двоичного

формата. Однако, игнорируя левую 6 и прибавив 6 к правой шест.C: шест.C +

6 = шест.12 - получим правильный результат в десятичном формате.

Правильный пример слегка упрощен, но он хорошо демонстрирует процесс,

который выполняет команда AAA при коррекции.

В качестве примера, предположим, что регистр AX содержит шест.0038, а

регистр BX - шест.0034. Числа 38 и 34 представляют два байта в

ASCII-формате, которые необходимо сложить. Сложение и коррекция кодируется

следующими командами:

ADD AL,BL ;Сложить 34 и 38

AAA ;Коррекция для сложения ASCII-кодов

Команда AAA проверяет правую шест. цифру (4 бита) в регистре AL. Если эта

цифра находится между A и F или флаг AF равен 1, то к регистру AL

прибавляется 6, а к регистру AH прибавляется 1, флаги AF и CF

устанавливаются в 1. Во всех случаях команда AAA устанавливает в 0 левую

шест. цифру в регистре AL. Результат - в регистре AX:

После команды ADD: 006C

После команды AAA: 0102

Для того, чтобы выработать окончательное ASCII-представление,

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

OR AX,3030H ;Результат 3132

Все показанное выше представляет сложение однобайтовых чисел.

Сложение многобайтовых ASCII-чисел требует организации цикла, который

выполняет обработку справа налево с учетом переноса. Пример , показанный

на рис.13.1 складывает два трехбайтовых ASCII-числа в четырехбайтовую

сумму. Обратите внимание на следующее:

__________

TITLE ASCADD (COM) Сложение чисел в ASCII-формате

CODESG SEGMENT PARA 'Code'

ASSUME CS:CODESG,DS:CODESG,SS:CODESG

ORG 100H

BEGIN: JMP SHORT MAIN

; -----------------------------------------------

ASC1 DB '578' ;Элементы данных

ASC2 DB '694'

ASC3 DB '0000'

; -----------------------------------------------

MAIN PROC NEAR

CLC

LEA SI,AASC1+2 ;Адреса ASCII-чисел

LEA DI,AASC2+2

LEA BX,AASC1+3

MOV CX,03 ;Выполнить 3 цикла

A20:

MOV AH,00 ;Очистить регистр AH

MOV AL,[SI] ;Загрузить ASCII-байт

ADC AL,[DI] ;Сложение (с переносом)

AAA ;Коррекция для ASCII

MOV [BX],AL ;Сохранение суммы

DEC SI

DEC DI

DEC BX

LOOP A20 ;Циклиться 3 раза

MOV [BX],AH ;Сохранить перенос

RET

MAIN ENDP

CODESG ENDS

END BEGIN

__________

Рис.13.1. Сложение в ASCII-формате.

- В программе используется команда ADC, так как любое сложение

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

(слева) байту. Команда CLC устанавливает флаг CF в нулевое состояние.

- Команда MOV очищает регистр AH в каждом цикле, так как команда

AAA может прибавить к нему единицу. Команда ADC учитывает пеpеносы.

Заметьте, что использование команд XOR или SUB для oчистки регистра

AH изменяет флаг CF.

- Когда завершается каждый цикл, происходит пересылка

содержимого pегистра AH (00 или 01) в левый байт суммы.

- В результате получается сумма в виде 01020702. Программа не

использует команду OR после команды AAA для занесения левой тройки,

так как при этом устанавливается флаг CF, что изменит pезультат

команды ADC. Одним из решений в данном случае является сохранение

флагового регистра с помощью команды PUSHF, выполнение команды OR, и,

затем, восстановление флагового регистра командой POPF:

ADC AL,[DI] ;Сложение с переносом

AAA ;Коррекция для ASCII

PUSHF ;Сохранение флагов

OR AL,30H ;Запись левой тройки

POPF ;Восстановление флагов

MOV [BX],AL ;Сохранение суммы

Вместо команд PUSHF и POPF можно использовать команды LAHF (Load AH

with Flags - загрузка флагов в регистр AH) и SAHF (Store AH in Flag

register - запись флагов из регистра AH во флаговый регистр). Команда LAHF

загружает в регистр AH флаги SF, ZF, AF, PF и CF; а команда SAHF

записывает содержимое регистра AH в указанные флаги. В приведенном

примере, однако, регистр AH уже используется для арифметических

переполнений. Другой способ вставки троек для получения ASCII-кодов цифр -

организовать обработку суммы командой OR в цикле.

Вычитание в ASCII-формате

---------------------------

Команда AAS (ASCII Adjust for Subtraction - коррекция для вычитания

ASCII-кодов) выполняется aналогично команде AAA. Команда AAS проверяет

правую шест. цифру (четыре бита) в регистре AL. Если эта цифра лежит между

A и F или флаг AF равен 1, то из регистра AL вычитается 6, а из регистра

AH вычитается 1, флаги AF и CF устанавливаются в 1. Во всех случаях

команда AAS устанавливает в 0 левую шест.цифру в регистpе AL.

В следующих двух примерах предполагается, что поле ASC1 содержит

шест.38, а поле ASC2 - шест.34:

Пример 1: AX AF

MOV AL,ASC1 ;0038

SUB AL,ASC2 ;0034 0

AAS ;0004 0

Пример 2: AX AF

MOV AL,ASC2 ;0034

SUB AL,ASC1 ;00FC 1

AAS ;FF06 1

В примере 1 команде AAS не требуется выполнять коррекцию. В примере 2, так

как правая цифра в регистре AL равна шест.C, команда AAS вычитает 6 из

регистра AL и 1 из регистра AH и устанавливает в 1 флаги AF и CF.

Результат (который должен быть равен -4) имеет шест. представление FF06,

т.е. десятичное дополнение числа -4.

Умножение в ASCII-формате

---------------------------

Команда AAM (ASCII Adjust for Multiplication - коррекция для

умножения ASCII-кодов) выполняет корректировку результата умножения

ASCII-кодов в регистре AX. Однако, шест. цифры должны быть очищены от

троек и полученные данные уже не будут являться действительными

ASCII-кодами. (В руководствах фирмы IBM для таких данных используется

термин pаспакованный десятичный формат). Например, число в ASCII-формате

31323334 имеет распакованное десятичное представление 01020304. Кроме

этого, надо помнить, что коррекция осуществляется только для одного байта

за одно выполнение, поэтому можно умножать только oдно-байтовые поля; для

более длинных полей необходима организация цикла.

Команда AAM делит содержимое регистра AL на 10 (шест.0A) и записывает

частное в регистр AH, а остаток в AL. Предположим, что в регистре AL

содержится шест.35, а в регистре CL - шест.39. Следующие команды умножают

содержимое регистра AL на содержимое CL и преобразуют результат в

ASCII-формат:

AX:

AND CL,0FH ;Преобразовать CL в 09

AND AL,0FH ;Преобразовать AL в 05 0005

MUL CL ;Умножить AL на CL 002D

AAM ;Преобразовать в распак.дес. 0405

OR AX,3030H ;Преобразовать в ASCII-ф-т 3435

Команда MUL генерирует 45 (шест.002D) в регистре AX, после чего команда

AAM делит это значение на 10, записывая частное 04 в регистр AH и остаток

05 в регистр AL. Команда OR преобpазует затем распакованное десятичное

число в ASCII-формат.

Пример на рис.13.2 демонстрирует умножение четырехбайтового множимого

на однобайтовый множитель. Так как команда AAM может иметь дело только с

однобайтовыми числами, то в программе организован цикл, который

обрабатывает байты справа налево. Окончательный результат умножения в

данном примере - 0108090105.

Если множитель больше одного байта, то необходимо обеспечить еще один

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

преобразовать число из ASCII-формата в двоичный формат (см. следующий

раздел "Преобразование ASCII-формата в двоичный формат").

__________

TITLE ASCMUL (COM) Умножение ASCII-чисел

CODESG SEGMENT PARA 'Code'

ASSUME CS:CODESG,DS:CODESG,SS:CODESG

ORG 100H

BEGIN: JMP MAIN

; ---------------------------------------------

MULTCND DB '3783' ;Элементы данных

MULTPLR DB '5'

PRODUCT DB 5 DUP(0)

; ---------------------------------------------

MAIN PROC NEAR

MOV CX,04 ;4 цикла

LEA SI,MULTCND+3

LEA DI,PRODUCT+4

AND MULTPLR,0FH ;Удалить ASCII-тройку

A20:

MOV AL,[SI] ;Загрузить ASCII-символ

; (можно LODSB)

AND AL,OFH ;Удалить ASCII-тройку

MUL MULTPLR ;Умножить

AAM ;Коррекция для ASCII

ADD AL,[DI] ;Сложить с

AAA ; записанным

MOV [DI],AL ; произведением

DEC DI

MOV [DI],AH ;Записать перенос

DEC SI

LOOP A20 ;Циклиться 4 раза

RET

MAIN ENDP

CODESG ENDS

END BEGIN

__________

Рис.13.2. Умножение в ASCII-формате.

Деление в ASCII-формате

-------------------------

Команда AAD (ASCII Adjust for Division - коррекция для деления

ASCII-кодов) выполняет корректировку ASCII-кода делимого до

непосредственного деления. Однако, прежде необходимо очистить левые тройки

ASCII-кодов для получения распакованного десятичного формата. Команда AAD

может оперировать с двухбайтовыми делимыми в регистре AX. Предположим, что

регистр AX содержит делимое 3238 в ASCII-формате и регистр CL содержит

делитель 37 также в ASCII-формате. Следующие команды выполняют коррекцию

для последующего деления:

AX:

AND CL,0FH ;Преобразовать CL в распак.дес.

AND AX,0F0FH ;Преобразовать AX в распак.дес. 0208

AAD ;Преобразовать в двоичный 001C

DIV CL ;Разделить на 7 0004

Команда AAD умножает содержимое AH на 10 (шест.0A), прибавляет pезультат

20 (шест.14) к регистру AL и очищает регистр AH. Значение 001C есть шест.

представление десятичного числа 28. Делитель может быть только

однобайтовый от 01 до 09.

Пример на рис.13.3 выполняет деление четырехбайтового делимого на

однобайтовый делитель. В программе организован цикл обработки делимого

справа налево. Остатки от деления находятся в регистре AH и команда AAD

корректирует их в регистре AL. Окончательный pезультат: частное 00090204 и

в регистре AH остаток 02.

Если делитель больше одного байта, то необходимо построить другой

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

"Преобразование ASCII-формата в двоичный формат."

__________

TITLE ASCDIV (COM) Деление ASCII-чисел

CODESG SEGMENT PARA 'Code'

ASSUME CS:CODESG,DS:CODESG,SS:CODESG

ORG 100H

BEGIN: JMP SHORT MAIN

; ---------------------------------------------

DIVDND DB '3698' ;Элементы данных

DIVSOR DB '4'

QUOTNT DB 4 DUP(0)

; ---------------------------------------------

MAIN PROC NEAR

MOV CX,04 ;4 цикла

SUB AH,AH ;Стереть левый байт делимого

AND DIVSOR,0FH ;Стереть ASCII 3 в делителе

LEA SI,DIVDND

LEA DI,QUOTNT

A20:

MOV AL,[SI] ;Загрузить ASCII байт

; (можно LODSB)

AND AL,0FH ;Стереть ASCII тройку

AAD ;Коррекция для деления

DIV DIVSOR ;Деление

MOV [DI],AL ;Сохранить частное

INC SI

INC DI

LOOP A20 ;Циклиться 4 раза

RET

MAIN ENDP

CODEGS ENDS

END BEGIN

__________

Рис.13.3. Деление в ASCII-формате.

ДВОИЧНО-ДЕСЯТИЧНЫЙ ФОРМАТ (BCD)

________________________________________________________________

В предыдущем примере деления в ASCII-формате было получено частное

00090204. Если сжать это значение, сохраняя только правые цифры каждого

байта, то получим 0924. Такой формат называется двоично-десятичным (BCD -

Binary Coded Decimal) (или упакованным). Он содержит только десятичные

цифры от 0 до 9. Длина двоично-десятичного представления в два раза меньше

ASCII-представления.

Заметим, однако, что десятичное число 0924 имеет основание 10 и,

будучи преобразованным в основание 16 (т.е. в шест. представление), даст

шест.039C.

Можно выполнять сложение и вычитание чисел в двоично-десятичном

представлении (BCD-формате). Для этих целей имеются две корректиpующих

команды:

DAA (Decimal Adjustment for Addition - десятичная коррекция для сложения)

DAS (Decimal Adjustment for Subtraction - десятичн. коррекция для вычит.)

Наши рекомендации