Арифметические операции языка Ассемблер
В рассмотренных примерах первый столбец таблицы содержит двоичные значения складываемых переменных, следующие 2 столбца показывают результат, если складываемые переменные интерпретируются как беззнаковые числа (значения переменных переводятся в десятичную систему счисления и складываются), далее 4-ый и 5-ый столбец показывают результат, если эти же двоичные значения интерпретируются, как знаковые… Читать ещё >
Арифметические операции языка Ассемблер (реферат, курсовая, диплом, контрольная)
Прежде чем приступить к изучению арифметических команд, рассмотрим такую форму представления чисел, как десятичные числа.
Десятичные числа — специальный вид представления числовой информации, в основу которого положен принцип кодирования каждой десятичной цифры числа группой разрядов из 4-х бит. При этом каждый байт числа содержит 1 или 2 десятичные цифры в так называемом двоично-десятичном коде (BCD — Binary Coded Decimal), Микропроцессор может хранить такие числа в2-х форматах:
- — упакованный формат — в байте 2 десятичные цифры, при этом старшая цифра занимает старшие 4 бита, диапазон представления чисел в одном байте составляет 00−99;
- — неупакованный формат — в байте 1 цифра в 4 младших битах. Старшие биты все имеют нулевое значение и называются зоной.
Описываются BCD-числа неупакованные, как DB, а упакованные как DT. Цифры неупакованного числа перечисляются через «,» а упакованногокак обычное десятичное число, например:
PER1 DB 2,3,4,5,6,8,2 ;неупаков. 2 865 432.
PER2 DT 9 875 645; упаков. 9 875 645.
Как мы увидим из рассмотрения арифметических команд, описание BCD-чисел и алгоритм их обработки являются делом вкуса программиста.
Сложение и вычитание целых чисел Кроме уже рассмотренной команды INC сложение осуществляют команды число арифметический команда алгоритм.
ADD операнд1, операнд2 — (операнд1=операнд1+операнд2)или.
ADD приемник, источник- (приемник = приемник + источник).
ADC операнд1, операнд2 — (операнд1=операнд1+операнд2 + значение cf) или.
ADC приемник, источник — (приемник = приемник + источник + перенос) Как видно из таблицы, специальных команд сложения десятичных чисел нет. Объясняется это тем, что микропроцессор все операнды интерпретирует как двоичные числа и складывает их по правилам сложения двоичных чисел (ранее мы с Вами их, эти правила, рассматривали). Операндами могут быть как 8-ми битовые, так и 16-ти битовые двоичные числа. Если результат операции не помещается в приемник, микропроцессор фиксирует ситуацию переполнения, устанавливая в 1 флаг переноса. Действия программы в этой ситуации могут быть следующими:
- — прекратить выполнение программы по переполнению разрядной сетки;
- — увеличить разрядность операндов.
Рассмотрим 2-ой вариант. Положим, складываются 8-миразрядные операнды из РОН AL и BL.
ADD AL, BL; результат можем превысить диапазон представления 8-ми разрядных чисел.
JNC M1; проверяем наличие переноса и при отсутствии -> на М1.
ADC AH, 0; расширяем разрядную сетку, добавив в результат РОН AH, теперь расширенный результат сложения; помещается в 16-битовом РОН AX M1:
В этом фрагменте программы можно было опустить команду условного переноса и сразу после ADD выполнить командуADC, однако следует учесть, что после метки М1 результат нужно выбирать из регистра АХ.
Аналогично можно складывать числа большей, чем 16 бит разрядности. Так, для сложения 32-битовых операндов (двойное слово) одно слагаемое можно поместить в РОН AX иBX, а второе — в DX и CX.
SLAG1 DD 4bf8ff31h.
SLAG2 DD a5244986h.
MOV AX, SLAG1 ;младшая часть.
MOV BX, SLAG1+2 ;старшая часть.
MOV CX, SLAG2.
MOV DX, SLAG2+2.
ADD AX, CX.
ADC BX, DX.
Команды ADD и ADC могут воздействовать на 6 флагов:
- — флаг переноса CF — устанавливается в 1, если результат сложения не помещается в операнде-приемнике, и противном случае CF=0;
- — флаг четности PF=1, если результат имеет четное число битов со значением 1, и противном случае PF=0;
- — вспомогательный флаг переноса AF=1, если результат сложения десятичных чисел требует коррекции, в противном случае он равен 0;
- — флаг нуля ZF=1, если результат сложения равен 0;
- — флаг знака SF=1, если сумма отрицательна (старший бит числа со знаком равен 1), в противном случаеSF=0;
- — флаг переполнения OF=1, если сложение чисел одного знака приводит к результату, который превышает диапазон допустимых значений приемника в обратном коде, а сам приемник при этом меняет знак (рекомендую просмотреть параграф «сложение двоичных чисел со знаком» на стр.160−162 (учебник Юрова «Ассемблер»).
Чтобы уяснить, как происходит сложение десятичных чисел, рассмотрим такой пример: сложим числа 26 и 55 в упакованном виде:
- 0010 0110
- 0101 0101
- 0111 1011
Полученный результат 7 В не является десятичным упакованным числом. Поэтому результат должен быть скорректирован для представления в десятичном виде. Для этих целей разработчики Ассемблера предложили 2 операции корректировки результата сложения:
ААА — корректировка результата для представления в кодахASCII;
DAA — корректировка результата для представления в упакованном десятичном формате.
Обе команды не имеют операндов и по умолчанию корректируют значение из регистра AL.
Команда ААА преобразует содержимое регистра AL в правильную неупакованную десятичную цифру в младших 4-х битах регистра, а старшие 4 бита заполняет нулями.
Используется команда в контексте.
ADD AL, BL; сложить неупакованные цифры, находящиеся в регистрах.
AAA; AL и BL и преобразовать результат в правильное десятичное число Если результат операции превышает 9, то команда ААА добавляет 1 к содержимому регистра AH и устанавливает в 1 флаг CF, в противном случае флаг устанавливается в 0. Поскольку для дальнейшего анализа имеет значение только состояние флага CF, остальные флаги нужно считать неопределенными (иначе говоря, их значения после команды ААА нельзя использовать для анализа — команд условного перехода).
Команда DAA преобразует содержимое регистра AL в 2 правильные упакованные десятичные цифры. Она используется в следующем контексте:
ADD AL, BL; сложить упакованные BCD-числа в регистрах ALи BL.
DAA; и преобразовать результат в упакованное число Если результат превышает предельное значение для упакованных BCD-чисел, то DAA добавляет 1 к содержимому регистра AH и устанавливает в 1 флаг CF, в противном случае флаг устанавливается в 0. Замечание относительно остальных флагов для команды DAA такие же, как и для команды ААА.
Как уже говорилось ранее, микропроцессор не имеет устройства вычитания, а имеет только устройство сложения (сумматор). Вычитание на таком устройстве осуществляется в 2 этапа:
- 1) меняется знак у вычитаемого — 2-го операнда или источника (иначе говоря, вычитаемое обращается);
- 2) складываются уменьшаемое и обращенное вычитаемое.
Для обращения операнда в системе команд имеется самостоятельная команда NEG приемник.
Эта команда вычитает значение операнда-приемника из 0 и, тем самым, формирует дополнительный код операнда (не забывайте, что дополнительный код числа в дополнительном коде будет являться модулем числа, или, иначе, обращение отрицательного числа даст число положительное). Установка флагов в этой команде осуществляется также, как и в команде вычитания.
Команды вычитания SUB и SBB аналогичны соответствующим командам сложения, только при вычитании флаг CFпонимается как признак заема.
SUB приемник, источник- (приемник = приемник — источник).
SBB приемник, источник — (приемник = приемник — источник — перенос_заем) Как и в случае сложения, команда SUB вычитает числа размером в байт или слово, а также младшие байты чисел повышенной точности. Совокупность команд SUB и SBBпозволяет вычитать операнды повышенной точности (двойное слово), например:
per1 DD 4bf8ff31h.
per2 DD a5244986h.
MOV AX, per1 ;младшая часть.
MOV BX, per1+2 ;старшая часть.
MOV CX, per2.
MOV DX, per2+2.
SUB AX, CX.
SBB BX, DX.
Здесь 32-битовое число из регистров CX и DX вычитается из 32-битового числа, помещенного в регистры AX и BX. Ограничение при вычитании — нельзя вычесть значение регистра или ячейки памяти из константы, поскольку, например, команда SUB 100, AL недопустима. Однако, если заменить недопустимую операцию 2-мя следующими.
NEG AL.
ADD AL, 100.
то вычитание из непосредственного значения будет выполнено и результат получен в AL.
Аналогично сложению, корректируются результаты вычитания при операциях с BCD-числами. Операция AAS корректирует результат вычитания неупакованной десятичной цифры из другой неупакованной десятичной цифры. Команда не имеет операндов и работает с регистром AL по следующему алгоритму:
- 1) если значение в регистре меньше или равно 9, то флаг CFустанавливается в 0 и управление передается следующей команде;
- 2) если значение в регистре AL больше 9,
- а) из содержимого младшей тетрады этого регистра вычитается 6,
- б) обнуляется старшая тетрада регистра AL;
- в) флаг CF устанавливается в 1, тем самым фиксируя наличие заема из предыдущего воображаемого разряда.
Проблему в операциях с десятичными числами создают ситуации, когда уменьшаемое меньше вычитаемого. В этом случае результат является отрицательным, однако, для десятичных чисел нет представления отрицательных значений. Выявление и обработка таких ситуаций полностью ложится на плечи программиста, что свидетельствует о том, что алгоритмы арифметических действий в Ассемблере нужно разрабатывать более тщательно и подробнее, чем в языках программирования высокого уровня.
Для упакованных BCD-чисел коррекцию результата вычитания производят командой DAS. Как и в предыдущей команде коррекции, результат предполагается в регистре AL, но теперь в обеих его тетрадах.
Дадим некоторые пояснения относительно арифметического переноса и переполнения.
Перенос — это выход за пределы разрядной сетки, переполнение — неверный результат операции из-за недостаточной разрядности сетки.
В рассмотренных примерах первый столбец таблицы содержит двоичные значения складываемых переменных, следующие 2 столбца показывают результат, если складываемые переменные интерпретируются как беззнаковые числа (значения переменных переводятся в десятичную систему счисления и складываются), далее 4-ый и 5-ый столбец показывают результат, если эти же двоичные значения интерпретируются, как знаковые значения. Последние 6-ой и 7-ой столбцы таблицы показывают, как устанавливаются флаги переноса (CF) и переполнения (ОF) при выполнении операции суммирования (ADD).
Проанализировав данные из таблицы, сделайте вывод:
- — если складываются беззнаковые переменные, о выходе результата за разрядную сетку свидетельствует установка в 1 флага переноса CF;
- — если складываются знаковые переменные, о неверном результате свидетельствует установка в 1 флага переполнения OF (обратитесь к описанию регистра флагов в лекции 4).
Из рассмотренных примеров следует вывод, что нужно четко представлять себе величины подлежащих обработке чисел и выбирать числовые элементы данных (описания переменных в сегменте данных) подходящих типов. Особенно легко приводит к переполнению арифметические операции с байтовыми знаковыми переменными, поскольку диапазон представления чисел в таких переменных составляет от -128 до 127.
Умножение и деление целых чисел В отличие от сложения и вычитания в микропроцессоре существуют по 2 операции умножения и деления: отдельно для чисел без знака и для чисел со знаком.
Инструкции MUL используется для умножения беззнаковых величин, а инструкция IMUL — для умножения знаковых чисел. Обе инструкции могут изменять состояние флагов переноса CF и переполнения OF. На программиста возлагается задание формата данных, подлежащих обработке, и выбор подходящей инструкции умножения.
Формат инструкций следующий:
MUL регистр
MUL память.
IMUL регистр
IMUL память.
Можно умножать байт на байт, слово на слово и в 32-разрядных моделях Intel — двойное слово на двойное слово.
Как видно из формата, в команде указывается только один из сомножителей, второй сомножитель по умолчанию располагается в регистре AL для байтовых операндов и в регистре АХ — для двухбайтовых операндов.
Примеры команды MUL:
BYTE1 DB 80H.
BYTE2 DB 40H.
WORD1 DW 8000H.
WORD2 DW 2000H.
MOV AL, BYTE1 ;байт умножается на байт.
MUL BYTE2 ;результат в АХ, равный 2000Н (8192=128*64).
(если до команды MOV AL, BYTE1 в АН содержались какие-либо данные, командой MUL BYTE2 эти данные будут затерты).
MOV AX, WORD1 ;слово умножается на слово.
MUL WORD2 ;результат в в DX: AX, равный 1000 0000Н Как видно из таблицы, операнды-сомножители должны иметь одинаковый формат: либо оба операнда — байты, либо оба операнда — слова. Если потребуется умножить байт на слово, необходимо сначала привести в соответствие размеры операндов. В Ассемблере существуют команды CBWпреобразовать байт в слово и CWD — преобразовать слово в двойное слово. Обе команды не имеют операндов. Команда CBWпо умолчанию оперирует содержимым регистра AL, а результат помещает в регистр AX. Команда CWD по умолчанию выбирает слово из регистра AX и помещает результат в DX: AX. Преобразование заключается в заполнении битом знакового разряда старших регистров результата — для команды CBW — регистра АН, а для команды CWD — регистра DX.
СBW — преобразовать байт в регистре AL в слово в регистре АХ путем распространения старшего бита AL на все биты регистра AH;
CWDпреобразовать слово в регистре АХ в двойное слово в регистрах AX и DX, путем распространения старшего 15-ого бита регистра АХ на все биты регистра DX. Эти команды позволяют приводить разноформатные операнды к одному формату (большему).
Обе команды дают верный результат при работе со знаковыми данными, но могут давать ошибочные результат с беззнаковыми данными. Например,.
MOV AL, BYTE2.
CBW.
позволит получить результат в АХ, равный 0020Н, а команды.
MOV AL, BYTE1.
CBW.
даст результат в АХ, равный FF80H, что, конечно, неверно.
Поэтому для беззнаковых данных при необходимости преобразования к большему формату следует заполнить нулями старшую часть операнда расширенного формата и оперировать в последующих командах расширенным операндом, например:
MOV AL, BYTE1.
MOV AH, 0 ;поместили сомножитель в регистр АХ.
MUL WORD2 ;результат в в DX: AX, равный 1 0000Н Команда IMUL реализует перемножение знаковых чисел. Результаты применения этой инструкции к приведенным выше данным будут следующие:
MOV AL, BYTE1 ;байт умножается на байт.
IMUL BYTE2 ;результат в АХ, равный E000Н (-8192=-128*64).
MOV AX, WORD1 ;слово умножается на слово.
MUL WORD2 ;результат в в DX: AX, равный F0000000Н.
MOV AL, BYTE1.
CBW ;поместили сомножитель в регистр АХ (FF80H).
IMUL WORD1 ;результат в в DX: AX, равный 0040 0000Н.
После выполнения команд флаги СF и OF показывают какая часть произведения существенна для дальнейших операций. При умножении чисел без знака эти флаги равны 0, если старшая часть результата нулевая, в противном случае (результат превысил по значащим цифрам сомножители) флаги устанавливаются в 1. Приумножении чисел со знаком флаги равны 0, если старшая половина произведения содержит расширение знакового разряда младшей половины (при положительном результате это 0, при отрицательном -1). Обратите внимание, что эти операции не позволяют иметь в качестве операнда константу (непосредственное значение).
Результат перемножения правильных неупакованных BCD-чисел может быть представлен в неупакованном форматеBCD-чисел с помощью команды ААМ. Команда работает с регистрами AL и AH и выполняет следующее: делит значение регистра AL на 10 и запоминает частное в регистре АН (старшая неупакованная цифра результата) и остаток — регистре AL (младшая неупакованная цифра результата). Однако очевидно, что этими операциями можно выполнить только табличное умножение. Для более сложных операций умножения необходимо разработать программу, реализующую умножение «в столбик», получение частных произведений, их сдвиги и сложение. Операции, аналогичной ААМ для упакованных BCD-чисел в микропроцессоре не существует.
В операции деления также, как и при умножении учитывается знак: операция DIV обрабатывает беззнаковые числа, а операция IDIV — знаковые. Подходящую команду в каждом конкретном случае выбирает программист.
Основные форматы операций деления делят слово на байт, двойное слово на слово. А в 32-разрядных моделях компьютеров еще и учетверенное слово на двойное слово.
Формат инструкций следующий:
DIV регистр
DIV память.
IDIV регистр
IDIV память.
Как видно из формата, в команде указывается только делитель, делимое по умолчанию располагается в регистре AХ или в паре регистров DX: АХ .
Примеры команды DIV:
BYTE1 DB 80H.
BYTE2 DB 16H.
WORD1 DW 2000H.
WORD2 DW 0010H.
WORD3 DW 1000H.
MOV AX, WORD1 ;делимое -> в АХ.
DIV BYTE1 ;получим частное вAL, остаток — AH.
;в AL — значение 40Н (8192:128=64-частное и 0 -остаток), в АН -00Н Интерпретируем значение в BYTE1 как беззнаковое и разделим его на значение из BYTE2.
MOV AH, 0 ;подготавливаем старшую часть делимого.
MOV AL, BYTE1 ;заполняем младшую часть делимого.
DIV BYTE2 ;делим слово на байт, в AL 05Н, в AH — 12Н При делении может возникать прерывание «деление на 0». Такой результат может получиться не только, когда делитель равен 0, но и в следующих случаях:
- 1) при делении чисел без знака для ситуации первой строки таблицы делимое более чем в 256 раз больше делителя,
- 2) при делении чисел без знака для ситуации второй строки таблицы делимое более чем в 65 636 раз больше делителя,
- 3) при делении чисел со знаком для ситуации первой строки таблицы делимое более чем в 128 раз больше значения делителя,
- 4) при делении чисел со знаком для ситуации второй строки таблицы делимое более чем в 32 768 раз больше значения делителя.
Приведу некоторые полезные приемы программирования.
Если частное слишком велико, можно выполнить деление с помощью последовательного вычитания делителя из делимого, например, делимое представляет слово, а делитель — байт, однако результат деления превышает байт (численные значения могут быть такими: делимое-64 000, делитель — 80, результат деления 800 превышает максимальное значение, хранимое в одном байте). Фрагмент программы при условии, что делимое находится в регистре АХ, а делитель — в регистре ВХ, может быть следующим:
SUB CX, CX ;очистка регистра частного.
L20: CMP AX, BX ;если делимое меньше делителя.
JB L30 ;вычитания закончить.
SUB AX, BX ;вычесть делитель из делимого.
INC CX ;увеличить частное на 1.
JMP L20 ;идти на начало цикла вычитания.
L30:. .. .. .. .
При попадании в точку L30 СХ содержит частное, АХ — остаток.
Если делимое — двойное слово в паре DX: AX, то.
SUB CX, CX ;очистка регистра частного.
L20: CMP DX, 0 ;пока DX не 0 — делимое больше делителя.
JNE L30.
CMP AX, BX ;если делимое меньше делителя.
JB L40 ;вычитания закончить.
L30: SUB AX, BX ;вычесть делитель из делимого.
SBB DX, 0 ;учесть возможный заем.
;SUB AX, BX ;вычесть делитель из делимого.
INC CX ;увеличить частное на 1.
JMP L20 ;идти на начало цикла вычитания.
L40:. .. .. .. .
Большое делимое и малый делитель вызовут многократное повторение команд!
Если необходимо разделить число на степени числа 2, то можно деление заменить сдвигами, например требуется разделить двойное слово в паре DX: AX на 16:
SHR AX, 04 ;делим младшую часть делимого.
MOV BL, DL ;сохраняем 3-ий байт в BL.
SHR DX, 04; делим старшую часть делимого.
SHL BL, 04 ;умножаем 3-ий байт на 16, поскольку младший полубайт старшего слова должен перейти в старший полубайт младшего слова результата.
OR AH, BL добавляем полубайт к 3-ему байту результата Процесс выполнения деления 2 неупакованных BCD-чисел может быть представлен в формате неупакованных BCD-чисел. Для этого перед операцией деления в регистре АХ получают 2 неупакованные цифры делимого (выполняет эту операцию программист удобным для него способом). Далее командой AAD преобразуется число в двоичное, которое затем является делимым в операции DIV. Причем в дальнейшей операции DIV двоичное число делится на неупакованнуюBCD-цифру, находящуюся в байтовом регистре или в байтовой ячейке памяти. Результат операции получается так, как описано 1-ой строкой таблицы. Понятно, что с применением этих команд можно выполнять очень простые операции деления, но команду AAD можно использовать и в контексте преобразования упакованного (или неупакованного) десятичного числа из диапазона 00−99 в двоичный эквивалент.
К группе арифметических команд относят команды расширения операнда, которые называются командами преобразования.
Рассмотрев арифметические операции можно сделать следующие выводы:
- 1) рассмотренная нами группа команд выполняет известные арифметические операции над целыми числами, для работы с вещественными числами применяют команды сопроцессора;
- 2) многие команды имеют только один операнд или не имеют операндов вообще, поскольку по умолчанию работают с определенными регистрами общего назначения;
- 3) арифметические операции чувствительны к размерности операндов, поэтому эту характеристику необходимо отслеживать программисту;
- 4) контроль над правильностью арифметических операций полностью лежит на программисте; программист должен отслеживать состояние арифметических флагов во время вычислительного процесса и принимать правильные решения по дальнейшей обработке;
- 5) для чисел со знаком установка в 1 флага OF говорит о том, что в результате сложения чисел одного знака результат выходит за границу допустимых значений чисел со знаком в данном формате и сам результат меняет знак (имеет другой знак, чем его операнды).