Автор: Пользователь скрыл имя, 10 Марта 2013 в 15:44, курс лекций
Один из основных принципов машины фон Неймана — то, что и программы, и данные хранятся в одной и той же памяти. Сохраняемая в памяти программа представляет собой некоторые коды, которые могут рассматриваться как данные. Возможно, с точки зрения программиста программа — активный компонент, она выполняет некоторые действия. Но с точки зрения процессора команды программы — это данные, которые процессор читает и интерпретирует. С другой стороны программа — это данные с точки зрения обслуживающих программ, например, с точки зрения компилятора, который на входе получает одни данные — программу на языке высокого уровня (ЯВУ), а на выходе выдает другие данные — программу в машинных кодах.
4
Умножение
Операция умножения для беззнаковых данных выполняется командой MUL, а для знаковых — IMUL (Integer MULtiplication — умножение целых чисел).
Ответственность за контроль над форматом обрабатываемых чисел и за выбор подходящей команды умножения лежит на самом программисте. Существуют две основные операции умножения:
Байт на байт
Множимое находится в регистре AL, а множитель в байте памяти или в однобайтовом регистре. После умножения произведение находится в регистре AX. Операция игнорирует и стиpает любые данные, которые находились в регистре AH.
Слово на слово
Множимое находится в регистре AX, а множитель — в слове памяти или в регистре. После умножения произведение находится в двойном слове, для которого требуется два регистра: старшая (левая) часть произведения находится в регистре DX, а младшая (правая) часть в регистре AX. Операция игнорирует и стирает любые данные, которые находились в регистре DX.
В единственном операнде команд MUL и IMUL указывается множитель. Рассмотрим следующую команду:
MUL MULTR
В случае, если поле MULTR определено как байт (DB), то операция предполагает умножение содержимого AL на значение байта из поля MULTR. В случае, если поле MULTR определено как слово (DW), то операция предполагает умножение содержимого AX на значение слова из поля MULTR. В случае, если множитель находится в регистре, то длина регистра определяет тип операции, как это показано ниже:
MUL CL ;Байт-множитель: множимое в AL, произвед. в AX
MUL BX ;Слово-множитель:множимое в AX, произвед. в DX:AX
Беззнаковое умножение: Команда MUL
Команда MUL (MULtiplication — умножение) умножает беззнаковые числа.
Знаковое умножение: Команда IMUL
Команда IMUL (Integer MULtiplication — умножение целых чисел) умножает знаковые числа.
Команда MUL рассматривает шест.80 как +128, а команда IMUL — как -128. В результате умножения -128 на +64 получается -8192 или шест.E000.
Если множимое и множитель имеет одинаковый знаковый бит, то команды MUL и IMUL генерируют одинаковый результат. Но, если сомножители имеют разные знаковые биты, то команда MUL вырабатывает положительный результат умножения, а команда IMUL — отрицательный.
Повышение эффективности умножения
При умножении на степень числа 2 (2,4,8 и так далее) более эффективным является сдвиг влево на требуемое число битов. Сдвиг более чем на 1 требует загрузки величины сдвига в регистр CL. В следующих примерах предположим, что множимое находится в регистре AL или AX:
Умножение на 2:
SHL AL,1
Умножение на 8:
MOV CL,3
SHL AX,CL
Многословное умножение
Обычно умножение имеет два типа: «байт на байт» и «слово на слово».
Как уже было
показано, максимальное знаковое значение
в слове ограничено величиной +32767.
Умножение больших чисел
1365
х
12
------
2730
1365
------
16380
Представим, что десятичная арифметика может умножать только двузначные числа. Тогда можно умножить 13 и 65 на 12 раздельно, cледующим образом:
13 65
х х
12 12
--- ---
26 130
13 65
--- ---
156 780
Следующим шагом сложим полученные произведения, но поскольку число 13 представляло сотни, то первое произведение в действительности будет 15600:
15600
+
780
-------
16380
Ассемблерная программа использует аналогичную технику за исключением того, что данные имеют размерность слов (четыре цифры) в шестнадцатеричном формате.
Умножение двойного слова на слово
Процедура E10XMUL умножает двойное слово на слово. Множимое, 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).
В силу обратного представления байтов в словах, область PRODUCT в действительности будет содержать значение 8A13 8066 00E4. Программа предполагает, что первое слово в области PRODUCT имеет начальное значение 0000.
Умножение двойного слова на двойное слово
Умножение двух двойных слов включает следующие четыре операции умножения:
Множимое Множитель
слово 2 слово 2
слово 2 слово 1
слово 1 слово 2
слово 1 слово 1
Каждое произведение в регистрах DX и AX складывается с соответствующим словом в окончательном результате.
Хотя логика умножения двойных слов аналогична умножению двойного слова на слово, имеется одна особенность, после пары команд сложения ADD/ADC используется еще одна команда ADC, которая прибавляет 0 к значению в итоговом поле.
Это необходимо потому, что первая команда ADC сама может вызвать перенос, который последующие команды могут стереть. Поэтому вторая команда ADC прибавит 0, если переноса нет, и прибавит 1, если перенос есть.
Финальная пара команд ADD/ADC не
требует дополнительной команды ADC,
так как область итога
4
Сдвиг регистровой пары 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
4
Деление
Операция деления для беззнаковых данных выполняется командой DIV, a для знаковых — IDIV. Ответственность за подбор подходящей команды лежит на программисте.
Существуют две основные операции деления:
Деление слова на байт
Делимое находится в регистре AX, а делитель — в байте памяти или а однобайтовом регистре. После деления остаток получается в регистре AH, а частное — в AL. Так как однобайтовое частное очень мало (максимально +255 (шест.FF) для беззнакового деления и +127 (шест.7F) для знакового), то данная операция имеет ограниченное использование.
Деление двойного слова на слово
Делимое находится
в регистровой паре DX:AX, а делитель
— в слове памяти или а регистре.
После деления остаток
В единственном операнде команд DIV и IDIV указывается делитель. Рассмотрим следующую команду:
DIV DIVISOR
В случае, если поле DIVISOR определено как байт (DB), то операция предполагает деление слова на байт. В случае, если поле DIVISOR определено как слово (DW), то операция предполагает деление двойного слова на слово.
При делении, например, 13 на 3, получается результат 4 1/3. Частное есть 4, а остаток — 1. Заметим, что ручной калькулятор выдает в этом случае результат 4,333.... Значение содержит целую часть (4) и дробную часть (,333). Значение 1/3 и 333... есть дробные части, в то время как 1 есть остаток от деления.
Беззнаковое деление: Команда DIV
Команда DIV делит беззнаковые числа.
Знаковое деление: Команда IDIV
Команда IDIV (Integer DIVide) выполняет деление знаковых чисел.
Повышение производительности
При делении на степень числа 2 (2, 4, и так далее) более эффективным является сдвиг вправо на требуемое число битов. В следующих примерах предположим, что делимое находится в регистре AX:
Деление на 2:
SHR AX,1
Деление на 8:
MOV CL,3
SHR AX,CL
Переполнения и прерывания
Используя команды DIV и особенно IDIV, очень просто вызвать пеpеполнение. Прерывания приводят (по крайней мере в системе, используемой при тестировании этих программ) к непредсказуемым результатам. В операциях деления предполагается, что частное значительно меньше, чем делимое.
Деление на ноль всегда вызывает прерывание. Но деление на 1 генерирует частное, которое равно делимому, что может также легко вызвать прерывание.
Рекомендуется использовать следующее правило: если делитель — байт, то его значение должно быть меньше, чем левый байт (AH) делителя: если делитель — слово, то его значение должно быть меньше, чем левое слово (DX) делителя.
При использовании команды 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.
Примечание: очень большое частное и малый делитель могут вызвать тысячи циклов.
4
Преобразование знака
Команда 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-коды удобны для отображения и печати, они требуют специальных преобразований в двоичный формат для арифметических вычислений.
Важно:
u Будьте особенно внимательны при использовании однобайтовых pегистров. Знаковые значения здесь могут быть от -128 до +127.
u Для многословного сложения используйте команду ADC для учета переносов от предыдущих сложений. В случае, если операция выполняется в цикле, то используя команду CLC, установите флаг переноса в 0.
u Используйте команды MUL или DIV для беззнаковых данных и команды IMUL или IDIV для знаковых.
u При делении будьте осторожны с переполнениями. В случае, если нулевой делитель возможен, то обеспечьте проверку этой операции. Кроме того, делитель должен быть больше содержимого регистра AH (для байта) или DX (для слова).
u Для умножения или деления на степень двойки используйте cдвиг. Сдвиг вправо выполняется командой SHR для беззнаковых полей и командой SAR для знаковых полей. Для сдвига влево используются идентичные команды SHL и SAL.