йта машинного кода, а LODS - только один, но требует инициализацию регистра SI. Можно использо вать команду LODS в том случае, когда требуется продвигаться вдоль строки (по байту или по слову), проверяя загружаемый регистр на конкретное значение. Команды, эквивалентные команде LODSB: MOV AL,[SI] INC SI Ассемблер для IBM PC. Глава 11 245 На рис.11.1 процедура E10LODS демонстрирует использование команды LODSW. В примере обрабатывается только одно слово: первый байт из области NAME1 (содержащий As) заносится в регистр AL, а второй байт - в регистр AH. В результате в регистре AX получится значение sA. STOS: ЗАПИСЬ СТРОКИ ------------------------------------------------------------ Команда STOS записывает (сохраняет) содержимое регистра AL или AX в байте или в слове памяти. Адрес памяти всегда представляется регистрами ES:DI. В зависимости от флага DF команда STOS также увеличивает или уменьшает адрес в регистре DI на 1 для байта или на 2 для слова. Практическая польза команды STOS с префиксом REP - инициализация области данных конкретным значением, например, очистка дисплейного буфера пробелами. Длина области (в байтах или в cловах) загружается в регистр AX. Команды, эквивалентные команде REP STOSB: JCXZ LABEL2 LABEL1: MOV [DI],AL INC/DEC DI ;Инкремент или декремент LOOP LABEL1 LABEL2: ... На рис.11.1 процедура F10STOS демонстрирует использование команды STOSW. Операция осуществляет запись шест. 2020 (пробелы) пять раз в область NAME3, причем значение из регистра AL заносится в первый байт, а из регистра AH - во второй. По завершении команды регистр DI содержит адрес NAME3+10. CMPS: СРАВНЕНИЕ СТРОК ------------------------------------------------------------ Команда CMPS сравнивает содержимое одной области памяти (адресуемой регистрами DS:SI) с содержимыми другой области (адресуемой как ES:DI). В зависимости от флага DF команда CMPS также увеличивает или уменьшает адреса в регистрах SI и DI на 1 для байта или на 2 для слова. Команда CMPS устанавливает флаги AF, CF, OF, PF, SF и ZF. При использовании префикса REP в регистре CX должна находиться длина сравниваемых полей. Команда CMPS может сравнивать любое число байт или слов. ------------------------------------------------------------ ------------------------------------------------------------ Рис. 11.1. Использование цепочечных команд. Рассмотрим процесс сравнения двух строк, содержащих имена JEAN и JOAN. Сравнение побайтно слева направо приводит к следующему: Ассемблер для IBM PC. Глава 11 246 J : J Равно E : O Не равно (E меньше O) A : A Равно N : N Равно Сравнение всех четырех байт заканчивается сравнением N:N - pавно/нуль. Так как имена "не равны", операция должна пре кратиться, как только будет обнаружено условие "не равно". Для этих целей команда REP имеет модификацию REPE, которая повторяет сравнение до тех пор, пока сравниваемые элементы равны, или регистр CX не pавен нулю. Кодируется повторяющее ся однобайтовое сравнение следующим образом: REPE CMPSB На рис.11.1 в процедере G10CMPS имеются два примера использования команды CMPSB. В первом примере происходит сравнение содержимого полей NAME1 и NAME2. Так как ранее команда MOVSB переслала содержимое поля NAME1 в поле NAME2, то команда CMPSB продолжается на всех десяти байтах и завершается состоянием pавно/нуль: флаг SF получает значение 0 (положительно) и флаг ZF - 1(нуль). Во втором примере сравнивается поля NAME2 и NAME3. Ранее команда STOSW заполнила поле NAME3 пробелами, поэтому команда CMPB завершается после сравнения первых же байт с результатом "больше/неравно": флаг SF получает значение 0 (положительно) и флаг ZF - 0 (ненуль). Первый пример заканчивается с результатом "равно/нуль" и заносит 01 в регистр BH. Второй пример заканчивается с результатом "неравно" и заносит 02 в регистр BL. При трассировке команд с помощью отладчика DEBUG можно увидеть, что в конце процедуры G10CMPS регистр BX будет содержать значение 0102. Предупреждение! Показанные примеры используют команду CMPSB для сравнения одного байта за одно выполнение. При использовании команды CMPSW для сравнения одного слова, необходимо инициализиpовать регистр CX значением 5. Кроме того следует помнить, что команда CMPSW при сравнении слов переставляет байты. Например, сравнивая имена SAMUEL и ARNOLD команда CMPSW выбирает вместо SA и AR переставленные значения, т.е. AS и RA. В результате вместо "больше" получится "меньше", т.е. неправельный результат. Таким образом команда CMPSW работает правильно только при сравне нии строк, которые содержат числовые данные, определенные как DW, DD или DQ. SCAS: СКАНИРОВАНИЕ СТРОК ------------------------------------------------------------ Команда SCAS отличается от команды CMPS тем, что сканирует (просматривает) строку на определенное значение байта или слова. Команда SCAS сравнивает содержимое области Ассемблер для IBM PC. Глава 11 247 памяти (адресуемой pегистрами ES:DI) с содержимым регистра AL или AX. В зависимости от значения флага DF команда SCAS также увеличивает или уменьшает адрес в регистре DI на 1 для байта или на 2 для слова. Команда SCAS устанавливает флаги AF, CF, OF, PF, SF и ZF. При использовании префикса REP и значения длины в регистре CX команда SCAS может сканировать строки любой длины. Команда SCAS особенно полезна, например, в текстовых редакторах, где программа должна сканировать строки, выполняя поиск знаков пунктуации: точек, запятых и пробелов. На рис.11.1 процедура H10SCAS сканирует область NAME1 на строчную букву "m". Так как команда SCASB должна продолжать сканирование, пока результат сравнения - "не равно" или регистр CX не равен нулю, то используется префикс REPNE: REPNE SCASB Так как область NAME1 содержит слово "Assemblers", то команда SCASB находит символ "m" в пятом сравнении. При использовании отладчика DEBUG для трассировки команд в конце процедуры H10SCAS можно увидеть в регистре AH значение 03 для индикации того, что символ "m" найден. Команда REP SCASB кроме того уменьшит значение регистра CX от 10 до 06. Команда SCASW сканирует в памяти слово на соответствие значению в регистре AX. При использовании команд LODSW или MOV для пересылки слова в регистр AX, следует помнить, что первый байт будет в регистре AL, а второй байт - в регистре AH. Так как команда SCAS сравнивает байты в обратной последовательности, то oперация корректна. СКАНИРОВАНИЕ И ЗАМЕНА ------------------------------------------------------------ В процессе обработки текстовой информации может возникнуть необходимость замены определенных символов в тексте на другие, например, подстановка пробелов вместо различных редактирующих символов. В приведенном ниже фрагменте программы осуществляется сканирование cтроки STRING и замена символа амперсанд (&) на символ пробела. Когда команда SCASB обнаружит символ & (в примере это будет позиция STRING+8), то операция сканирования прекратит ся и регистр DI будет содержать aдрес STRING+9. Для получе ния адреса символа & необходимо уменьшить содержимое DI на единицу и записать по полученному адресу символ пробела. STRLEN EQU 15 ;Длина поля STRING STRING DB 'The time&is now' ... CLD MOV AL,'&' ;Искомый символ MOV CX,STRLEN ;Длина поля STRING LEA DI,STRING ;Адрес поля STRING REPNE SCASB ;Сканировать Ассемблер для IBM PC. Глава 11 248 JNZ K20 ;Символ найден? DEC DI ;Да - уменьшить адрес MOV BYTE PTR[DI],20H ;Подставить пробел K20: RET АЛЬТЕРНАТИВНОЕ КОДИРОВАНИЕ ------------------------------------------------------------ При использовании команд MOVSB или MOVSW ассемблер предполагает наличие корректной длины строковых данных и не требует кодирования операндов в команде. Для команды MOVS длина должна быть закодирована в операндах . Например, если поля FLDA и FLDB определены как байтовые (DB), то команда REP MOVS FLDA,FLDB предполагает повторяющуюся пересылку байтов из поля FLDB в поле FLDA. Эту команду можно записать также в следующем виде: REP MOVS ES:BYTE PTR[DI],DS:[SI] Однако загрузка регистров DI и SI адресами FLDA и FLDB oбязательна в любом случае. ДУБЛИРОВАНИЕ ОБРАЗЦА ------------------------------------------------------------ Команда STOS бывает полезна для установки в некоторой области oпределенных значений байтов и слов. Для дублирова ния образца, длина которого превышает размер слова, можно использовать команду MOVS с небольшой модификацией. Предположим, что необходимо сформировать строку следующего вида: ***---***---***---***---***--- . . . Вместо того, чтобы определять полностью всю строку, можно определить только первые шесть байтов. Закодируем образец непосредственно перед обрабатываемой строкой следующим образом: PATTERN DB '***---' DISAREA DB 42 DUP(?) . . CLD MOV CX,21 LEA DI,DISAREA LEA SI,PATTERN REP MOVSW В процессе выполнения команда MOVSW сначала пересылает первое слово (**) из образца PATTERN в первое слово области DISAREA, затем - второе слово (*-), потом третье (--): Ассемблер для IBM PC. Глава 11 249 ***---***--- | | PATTERN DISAREA К этому моменту регистр DI будет содержать адрес DISAREA+6, а pегистр SI - PATTERN+6, который также является адресом DISAREA. Затем команда MOVSW автоматически дублирует образец, пересылая первое слово из DISAREA в DISAREA+6, из DISAREA+2, в DISAREA+8, из DISAREA+4 в DISAREA+10 и т.д. В результате образец будет полностью продублирован по всей области DISAREA: ***---***---***---***---***--- . . . ***--- | | | | PATTERN DISAREA+6 DISAREA+12 DISAREA+42 Данную технику можно использовать для дублирования в области памяти любого образца любой длины. Образец должен быть расположен непосредственно перед принимающей областью. ПРОГРАММА: ВЫРАВНИВАНИЕ ВПРАВО ПРИ ВЫВОДЕ НА ЭКРАН ------------------------------------------------------------ COM-программа, изображенная на рис.1.2, иллюстрирует почти весь материал, приведенный в этой главе. Процедуры программы выполняют следующие действия: B10INPT Принимает имена длиной до 30 символов, вводимых вверху экрана. D10SCAS Использует команду SCASB для сканирования имен и об хода любого ввода, содержащего символ "звездочка". E10RGHT Использует команду MOVSB для выравнивания имен по правой границе, выводит имена в колонку в правой части экрана. Длина в поле ACTNLEN из списка параметров ввода используется для вычисления самого правого символа в имени, например: JEROME KERN OSCAR HAMMERSTEIN RICHARD ROGERS F10CLNM Использует команду STOSW для очистки области имени в памяти. ------------------------------------------------------------ ------------------------------------------------------------ Рис.11.2. Выравнивание вправо при выводе на экран. ОСНОВНЫЕ ПОЛОЖЕНИЯ НА ПАМЯТЬ ------------------------------------------------------------ Ассемблер для IBM PC. Глава 11 250 ъ Для цепочечных команд MOVS, STOS, CMPS и SCAS не забы- вайте инициализировать регистр ES. ъ Сбрасывайте (CLD) или устанавливайте (STD) флаг направ ления в соответствии с направлением обработки. ъ Не забывайте устанавливать в регистрах DI и SI необходи мые значения. Например, команда MOVS предполагает операнды DI,SI, а команда CMPS - SI,DI. ъ Инициализируйте регистр CX в соответствии с количеством байтов или слов, участвующих в процессе обработки. ъ Для обычной обработки используйте префикс REP для команд MOVS и STOS и модифицированный префикс (REPE или REPNE) для команд CMPS и SCAS. ъ Помните об обратной последовательности байтов в сравни ваемых cловах при выполнении команд CMPSW и SCASW. ъ При обработке справа налево устанавливайте начальные адреса на последний байт обрабатываемой области. Если, например, поле NAME1 имеет длину 10 байтов, то для побайтовой обработки данных в этой области справа налево начальный адрес , загружаемый командой LEA, должен быть NAME1+9. Для бработки слов начальный адрес в этом случае - NAME1+8. ВОПРОСЫ ДЛЯ САМОПРОВЕРКИ ------------------------------------------------------------ 11.1. В данной главе приведены эквивалентные команды для а) MOVSB, б) LODSB и в)STOSB с префиксом REP. Напишите эквивалентные команды для обработки по словам а) MOVSW, б) LODSW и в) STOSW с префиксом REP. 11.2. Введите, ассемблируйте и выполните компановку программы, приведенной на рис.11.1. Не забудьте о инициализации регистра ES. Замените команды MOVSB и MOVSW для пересылки справа налево. Измените процедуру H10SCAS для сканирования поля NAME1 на слово "mb". Используя отладчик DEBUG для трассировки процедур, обратите веимание на содержимое сегмента данных и регистров. 11.3. Имеются следующие определения: DATASG SEGMENT PARA CONAME DB 'SPACE EXPLORERS INC.' PRLINE DB 20 DUP(' ') Используя цепочечные команды, выполните: а) пересылку данных из CONAME в PRLINE слева направо; Ассемблер для IBM PC. Глава 11 251 б) пересылку данных из CONAME в PRLINE справа налево; в) загрузку третьего и четвертого байтов области CONAME в регистр AX; г) сохранение содержимого регистра AX в область по адресу PRLINE+5; д) сравнение данных в областях CONAME и PRLINE (они должны быть не равны); е) сканирование областей CONAME и PRLINE, и поиск в ней символа пробел. Если символ будет найден, то переслать его в регистр BH. 11.4. Переделайте процедуру H10SCAS (рис.11.1) так, чтобы выполнялось сканирование поля NAME1 на символ "er". Обратите внимание, что символы "er" не встречаются в поле NAME1 как одно слово: /As/se/mb/le/rs/. Для решения этой проблемы возможны два варианта: а) использовать команду SCASW дважды, причем первая должна начинаться по адресу NAME1, а вторая - по адресу NAME1+1; б) использовать команду SCASB для поиска символа "е" и сравнить затем следующий байт на символ "r". 11.5. Определите поле, содержащее шест.значения 03, 04, 05 и B4. Продублируйте это поле 20 раз и выдайте результат на экран. Ассемблер для IBM PC. Глава 12 262 ГЛАВА 12. Арифметические операции I: Обработка двоичных данных ------------------------------------------------------------ Арифметические операции I: Обработка двоичных данных Цель: Дать сведения об операциях сложения, вычитания, умножения и деления двоичных данных. ВВЕДЕНИЕ ------------------------------------------------------------ Несмотря на то, что мы привыкли к десятичной арифметике (база 10), компьютер работает только с двоичной арифметикой (база 2). Кроме того, ввиду ограничения, накладываемого 16-битовыми регистрами, большие величины требуют специальной обработки. Данная глава дает сведения об операциях сложения, вычитания, умножения и деления для беззнаковых и знаковых данных. В главе приводятся много примеров и предупреждений о различных ловушках для опрометчивых исследователей мира микропроцессора. В следующей главе будут раскрыты операции преобразования между двоичными данными и ASCII кодами. СЛОЖЕНИЕ И ВЫЧИТАНИЕ ------------------------------------------------------------ Команды ADD и SUB выполняют сложение и вычитание байтов или слов, содержащих двоичные данные. Вычитание выполняется в компьютере по методу сложения с двоичным дополнением: для второго операнда устанавливаются обратные значения бит и прибавляется 1, а затем проиCXодит сложение с первым операндом. Во всем, кроме первого шага, операции сложения и вычитания идентичны. На рис. 12.1 представленны примеры команд ADD и SUB, обрабатывающие байты или слова. В процедуре B10ADD используется команда ADD для сложения байтов, а в процедуре C10SUB команда SUB вычитает слова. Примеры показывают все пять возможных ситуаций: сложение/вычитание регистр-регистр; сложение/вычитание память-регистр; сложение/вычитание регистр-память; сложение/вычитание регистр-непоср.значение; сложение/вычитание память-непоср.значение. ------------------------------------------------------------ ------------------------------------------------------------ Рис. 12.1 Примеры команд ADD и SUB. Поскольку прямой операции память-память не существует, данная oперация выполняется через регистр. В следующем примере к содержимому слова WORDB прибавляется содержимое слова WORDA, описанных как DW: Ассемблер для IBM PC. Глава 12 263 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 ограни чивает возможность компьютера для выполнения арифметических операций. Рассмотрим два способа выполнения арифметических операций. Первый способ - более прост, но специфичен, второй - сложнее, но имеет общий характер. ------------------------------------------------------------ ------------------------------------------------------------ Рис. 12.2. Сложение двойных слов. Ассемблер для IBM PC. Глава 12 264 На рис.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, получим процедуру для вычитания. БЕЗЗНАКОВЫЕ И ЗНАКОВЫЕ ДАННЫЕ ------------------------------------------------------------ Ассемблер для IBM PC. Глава 12 265 Многие числовые поля не имеют знака, например, номер абонента, aдрес памяти. Некоторые числовые поля предлагаются всегда положительные, например, норма выплаты, день недели, значение числа ПИ. Другие числовые поля являются знаковые, так как их содержимое может быть положительным или отрицательным. Например, долговой баланс покупателя, который может быть отрицательным при переплатах, или алгебраическое число. Для беззнаковых величин все биты являются битами данных и вместо ограничения +32767 регистр может содержать числа до +65535. Для знаковых величин левый байт является знаковым битом. Команды ADD и SUB не делают разницы между знаковыми и беззнаковыми величинами, они просто складывают и вычитают биты. В следующем примере сложения двух двоичных чисел, первое число содержит единичный левый бит. Для беззнакового числа биты представляют положительное число 249, для знакового - отрицательное число -7: Беззнаковое Знаковое 11111001 249 -7 00000010 2 +2 11111011 251 -5 Двоичное представление результата сложения одинаково для беззнакового и знакового числа. Однако, биты представляют +251 для беззнакового числа и -5 для знакового. Таким одразом, числовое содержимое поля может интерпретироваться по разному. Состояние "перенос" возникает в том случае, когда имеется пеpенос в знаковый разряд. Состояние "переполнение" возника ет в том случае, когда перенос в знаковый разряд не создает переноса из разрядной сетки или перенос из разрядной сетки проиCXодит без переноса в знаковый разряд. При возникновении переноса при сложении беззнаковых чисел, результат получает ся неправильный: Беззнаковое Знаковое 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 (неправильно) При операциях сложения и вычитания может одновременно возникнуть и переполнение, и перенос: Ассемблер для IBM PC. Глава 12 266 Беззнаковое Знаковое 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 Ассемблер для IBM PC. Глава 12 267 Беззнаковое умножение: Команда MUL Команда MUL (MULtiplication - умножение) умножает беззна ковые числа. На рис. 12.3 в процедуре C10MUL дано три примера умножения: байт на байт, слово на слово и слово на байт. Первый пример команды MUL умножает шест.80 (128) на шест.47 (64). Произведение -ш ест.2000 (8192) получается в регистре AX. ------------------------------------------------------------ ------------------------------------------------------------ Рис. 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 в Ассемблер для IBM PC. Глава 12 268 регистрах 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. Умножение больших чисел требует выполнения некоторых дополнительных действий. Рассматриваемый подход предполагает умножение каждого слова отдельно и сложение полученных результатов. Рассмотрим следующее умножение в десятичном формате: 1365 х12 2730 1365 16380 Представим, что десятичная арифметика может умножать только двухзначные числа. Тогда можно умножить 13 и 65 на 12 раздельно, cледующим образом: 13 65 х12 х12 26 130 13 65 156 780 Следующим шагом сложим полученные произведения, но поскольку число 13 представляло сотни, то первое произведение в действительности будет 15600: 15600 Ассемблер для IBM PC. Глава 12 269 +780 16380 Ассемблерная программа использует аналогичную технику за исключением того, что данные имеют размерность слов (четыре цифры) в шестнадцатеричном формате. Умножение двойного слова на слово. Процедура 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. ------------------------------------------------------------ ------------------------------------------------------------ Рис.12.4. Многословное умножение. Умножение "двойного слова на двойное слово". Умножение двух двойных слов включает следующие четыре операции умножения: Множимое Множитель слово 2 х слово 2 слово 2 х слово 1 слово 1 х слово 2 слово 1 х слово 1 Каждое произведение в регистрах DX и AX складывается с соответствующим словом в окончательном результате. Пример такого умножения приведен в процедуре F10XMUL на рис. 12.4. Ассемблер для IBM PC. Глава 12 270 Множимое 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 бита Ассемблер для IBM PC. Глава 12 271 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 дано четыре примера деления: слово на байт, байт на байт, двойное слово на слово и слово на слово. Ассемблер для IBM PC. Глава 12 272 Первый пример команды 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. ------------------------------------------------------------ ------------------------------------------------------------ Рис.15.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 Ассемблер для IBM PC. Глава 12 273 Деление на 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: Ассемблер для IBM PC. Глава 12 274 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-коды удобны для отображения и печати, они требуют специальных преобразований в двоичный формат для арифметических вычислений. Но это уже тема следующей главы. Ассемблер для IBM PC. Глава 12 275 ПРОЦЕССОРЫ 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. Ассемблер для IBM PC. Глава 12 276 ВОПРОСЫ ДЛЯ САМОПРОВЕРКИ ------------------------------------------------------------ Все вопросы имеют отношение к следующим данным: 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" является более эффективным по сравнению с предыдущими примерами для сдвига влево на четыре бита. Измените пример для сдвига вправо на четыре бита. Ассемблер для IBM PC. Глава 13 1 ГЛАВА 13. Арифметические операции II: ------------------------------------------------------------ Арифметические операции 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-формате: Ассемблер для IBM PC. Глава 13 2 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 34 Шест. 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 Ассемблер для IBM PC. Глава 13 3 Все показанное выше представляет сложение однобайтовых чисел. Сложение многобайтовых ASCII-чисел требует организа ции цикла, который выполняет обработку справа налево с учетом переноса. Пример , показанный на рис.13.1 складывает два трехбайтовых ASCII-числа в четырехбайтовую сумму. Обратите внимание на следующее: ъ В программе используется команда ADC, так как любое сложение может вызвать перенос, который должен быть прибавлен к следующему (слева) байту. Команда CLC устанавливает флаг CF в нулевое состояние. ------------------------------------------------------------ ------------------------------------------------------------ Рис. 13.1. Сложение в ASCII-формате. ъ Команда 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-формате Ассемблер для IBM PC. Глава 13 4 Команда 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 Ассемблер для IBM PC. Глава 13 5 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-формата в двоичный формат"). ------------------------------------------------------------ ------------------------------------------------------------ Рис.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. Ассемблер для IBM PC. Глава 13 6 Если делитель больше одного байта, то необходимо постро ить другой цикл для обработки делителя, но лучше воспользо ваться следующим разделом "Преобразование ASCII-формата в двоичный формат." ДВОИЧНО-ДЕСЯТИЧНЫЙ ФОРМАТ (BCD) ------------------------------------------------------------ В предыдущем примере деления в ASCII-формате было получено частное 00090204. Если сжать это значение, сохраняя только правые цифры каждого байта, то получим 0924. Такой формат называется двоично-десятичным (BCD - Binary Coded Decimal) (или упакованным). Он содержит только десятичные цифры от 0 до 9. Длина двоично-десятичного представления в два раза меньше ASCII-представления. ------------------------------------------------------------ ------------------------------------------------------------ Рис.13.3. Деление в ASCII-формате. Заметим, однако, что десятичное число 0924 имеет основание 10 и, будучи преобразованным в основание 16 (т.е. в шест. представление), даст шест.039C. Можно выполнять сложение и вычитание чисел в двоично-десятичном представлении (BCD-формате). Для этих целей имеются две корректиpующих команды: DAA (Decimal Adjustment for Addition - десятичная коррекция для сложения) DAS (Decimal Adjustment for Subtraction - десятичная коррекция для вычитания) Обработка полей также осуществляется по одному байту за одно выполнение. В примере программы, приведенном на рис. 13.4, выполняется преобразование чисел из ASCII-формата в BCD-формат и сложение их. Процедура B10CONV преобразует ASCII в BCD. Обработка чисел может выполняться как справа налево, так и слева направо. Кроме того, обработка слов проще, чем обработка байтов, так как для генерации одного байта BCD-кода требуется два байта ASCII-кода. Ориентация на обработку слов требует четного количества байтов в ASCII- поле. Процедура C10ADD выполняет сложение чисел в BCD-формате. Окончательный результат - 127263. ПРЕОБРАЗОВАНИЕ ASCII-ФОРМАТА В ДВОИЧНЫЙ ФОРМАТ ------------------------------------------------------------ bыполнение арифметических операций над числами в ASCII или BCD форматах удобно лишь для коротких полей. В боль шинстве случаев для арифметических операций используется преобразование в двоичный формат. Практически проще Ассемблер для IBM PC. Глава 13 7 преобразование из ASCII-формата непосредственно в двоичный формат, чем преобразование из ASCII- в BCD-формат и, затем, в двоичный формат: Метод преобразования базируется на том, что ASCII-формат имеет основание 10, а компьютер выполняет арифметические операции только над числами с основанием 2. Процедура преобразования заключается в следующем: 1. Начинают с самого правого байта числа в ASCII-формате и обрабатывают справа налево. 2. Удаляют тройки из левых шест.цифр каждого ASCII-байта. 3. Умножают ASCII-цифры на 1, 10, 100 (шест.1, A, 64) и т.д. и складывают результаты. Для примера рассмотрим преобразование числа 1234 из ASCII-формата в двоичный формат: Десятичное Шестнадцатиричное 4 х 1 = 4 4 3 х 10 = 30 1E 2 х 100 = 200 C8 1 х 1000 = 1000 3E8 Результат: 04D2 ------------------------------------------------------------ ------------------------------------------------------------ Рис. 13.4. BCD-преобразование и арифметика. Проверьте, что шест.04D2 действительно соответствует десятичному 1234. На рис. 13.5. в процедуре B10ASBI выполняется преобразоние ASCII-числа 1234 в двоичный формат. В примере предполагается, что длина ASCII-числа равна 4 и она записана в поле ASCLEN. Для инициализации адрес ASCII- поля ASCVAL-1 заносится в регистр SI, а длина - в регистр BX. Команда по метке B20 пересылает ASCII-байт в регистр AL: MOV AL,[SI+BX] Здесь используется адрес ASCVAL-1 плюс содержимое регистра BX (4), т.е. получается адрес ASCVAL+3 (самый правый байт поля ASCVAL). В каждом цикле содержимое регистра BX уменьшается на 1, что приводит к обращению к следующему слева байту. Для данной адресации можно использовать регистр BX, но не CX, и, следовательно, нельзя применять команду LOOP. В каждом цикле происходит также умножение поля MULT10 на 10, что дает в результате множители 1,10,100 и т.д. Такой прием применен для большей ясности, однако, для большей производительности множитель можно хранить в регистре SI или DI. ПРЕОБРАЗОВАНИЕ ДВОИЧНОГО ФОРМАТА В ASCII-ФОРМАТ ------------------------------------------------------------ Ассемблер для IBM PC. Глава 13 8 Для того, чтобы напечатать или отобразить на экране арифметический pезультат, необходимо преобразовать его в ASCII-формат. Данная операция включает в себя процесс обратный предыдущему. Вместо умножения используется деление двоичного числа на 10 (шест. 0A) пока результат не будет меньше 10. Остатки, которые лежат в границах от 0 до 9, образуют число в ASCII-формате. В качестве примера рассмотрим преобразование щест.4D2 обратно в десятичный формат: Частное Остаток 4D2 : A 7B 4 7B : A C 3 C : A 1 2 Так как последнее частное 1 меньше, чем шест.A, то операция завершена. Остатки вместе с последним частным образуют результат в ASCII-формате, записываемый справа налево 1234. Все остатки и последнее частное должны записываться в память с тройками, т.е. 31323334. На рис. 13.5. процедура C10BIAS преобразует шест. 4D2 (результат вычисления в процедуре B10ASBI) в ASCII-число 1234. Полезно переписать всю программу (рис.13.5.) в компьютер и выполнить трассиpовку ее выполнения по шагам. ------------------------------------------------------------ ------------------------------------------------------------ Рис.13.5. Преобразование ASCII и двоичного форматов. СДВИГ И ОКРУГЛЕНИЕ ------------------------------------------------------------ Рассмотрим процесс округления числа до двух десятичных знаков после запятой. Если число равно 12,345, то необходимо прибавить 5 к отбрасываемому разряду и сдвинуть число вправо на один десятичный разряд: Число: 12,345 Плюс 5: +5 Округленное число: 12,350 = 12,35 Если округляемое число равно 12,3455, то необходимо прибавить 50 и сдвинуть на два десятичных разряда. Для 12,34555 необходимо прибавить 500 и сдвинуть на три десятичных разряда: 12,3455 12,34555 +50 +500 12,3505 = 12,35 12,35055 = 12,35 К числу, имеющему шесть знаков после запятой, необходимо прибавить 5000 и сдвинуть на четыре десятичных разряда и т.д. Поскольку данные представляются в компьютере в двоичном Ассемблер для IBM PC. Глава 13 9 виде, то 12345 выглядит как шест.3039. Прибавляя 5 к 3039, получим 303E, что соответствует числу 12350 в десятичном представлении. Пока все хорошо. Но вот сдвиг на одну двоич ную цифру дает в результате шест.181F, или 1675 - т.е. сдвиг на одну двоичную цифру просто делит число пополам. Но нам необходим такой сдвиг, который эквивалентен сдвигу вправо на одну десятичную цифру. Такой сдвиг можно осуществить делением на 10 (шест.A): Шест.303E : Шест.A = 4D3 или дес.1235 Преобразование шест.4D3 в ASCII-формат дает число 1235. Теперь oстается лишь вставить запятую в правильную позицию числа 12,35, и можно выдать на экран округленное и сдвинутое значение. Таким образом можно округлять и сдвигать любые двоичные числа. Для трех знаков после запятой необходимо прибавить 5 и разделить на 10, для четырех знаков после запятой: прибавить 50 и pазделить на 100. Возможно вы заметили модель: фактор округления (5, 50, 500 и т.д.) всегда составляет половину фактора сдвига (10, 100, 1000 и т.д.). Конечно, десятичная запятая в двоичном числе только подpазумевается. ПРОГРАММА: ПРЕОБРАЗОВАНИЕ ВРЕМЕНИ И РАСЦЕНКИ РАБОТ ДЛЯ РАСЧЕТА ЗАРПЛАТЫ ------------------------------------------------------------ Программа, приведенная на рис.13.6, позволяет вводить с клавиатуры значения продолжительности и расценки работ и отображать на экран pасчитанную величину заработанной платы. Для краткости в программе опущены некоторые проверки на ошибку. Программа содержит следующие процедуры: B10INPT Вводит значения времени работы на ее расценку с клавиатуры. Эти значения могут содержать десятич ную запятую. D10HOUR Выполняет преобразование значения времени из ASCII в двоичный формат. E10RATE Выполняет преобразование значения расценки из ASCII в двоичный формат. F10MULT Выполняет умножение, округление и сдвиг. Величина зарплаты без дробной части или с одним или двумя знаками после запятой не требует округления и сдви га. Данная процедура ограничена тем, что позволяет обрабатывать величину зарплаты с точностью до шести десятичных знаков, что, конечно, больше, чем требуется. G10WAGE Вставляет десятичную запятую, определяет правую позицию для начала записи ASCII символов и преобразует двоичное значение зарплаты в ASCII-формат. Ассемблер для IBM PC. Глава 13 10 K10DISP Заменяет лидирующие нули на пробелы и выводит результат на экран. M10ASBI Преобразует ASCII в двоичный формат (общая процедура для времени и расценки) и определяет число цифр после запятой в введенном значении. ------------------------------------------------------------ ------------------------------------------------------------ Рис.13.6. Расчет заработной платы. Ограничения. Первое ограничение в программе, приведенной на рис.13.6, cостоит в том, что допускает не более шести десятичных знаков после запятой. Другое ограничение - размер самой зарплаты и тот факт, что сдвиг включает деление на число, кратное 10, a преобразование в ASCII-формат включает деление на 10. Если значение времени или расценки содержит больше шести десятичных знаков или зарплата превы шает величину около 655350, то программа выдает нулевой результат. На практике программа может предусмотреть в данном случае вывод предупреждающего сообщения или иметь подпрограммы для исключения таких ограничений. Контроль ошибок. Программа, разработанная для пользовате лей, не являющихся программистами, должна не только выдавать предупреждающие сообщения, но также проверять корректность вводимых значений. Правильными символами при вводе числовых значений являются цифры от 0 до 9 и символ десятичной запятой. Для любых других символов программа должна выдать предупреждающее сообщение и вновь повторить запрос на ввод. Полезной командой для проверки корректности вводимых символов является XLAT (см. главу 14). Тщательно проверяйте программы для любых возможных состояний: нулевое значение, максимально большие и малые значения, отрицательные значения. Отрицательные величины Некоторые применения программ допускают наличие отрицательных величин. Знак минус может устанавливаться после числа, например, 12,34-, или перед числом -12,34. Программа может проверять наличие минуса при преобразовании в двоичный формат. Можно оставить двоичное число положительным, но установить соответствующий индикатор исходной отрицательной величины. После завершения арифметических операций знак минус при необходимости может быть вставлен в ASCII поле. Если необходимо, чтобы двоичное число было также отрицательным, то можно преобразовать, как обычно, ASCII-формат в двоичный, а для изменения знака двоичного числа воспользоваться командами, описанными в главе 12 "Преобразование знака". Будьте внимательны при использовании Ассемблер для IBM PC. Глава 13 11 команд IMUL и IDIV для обработки знаковых данных. Для округления отрицательных чисел следует не прибавлять, а вычитать фактор 5. ОСНОВНЫЕ ПОЛОЖЕНИЯ НА ПАМЯТЬ ------------------------------------------------------------ ъ ASCII-формат требует один байт на каждый символ. Если поле содержит только цифры от 0 до 9, то замена старших троек в каждом байте на нули создает распакованный десятичный формат. Сжатие числа до двух цифр в байте создает упакованный десятичный формат. ъ После ASCII-сложения необходимо выполнить коррекцию с помощью команды AAA; после ASCII-вычитания - коррекция с помощью команды AAS. ъ Прежде чем выполнить ASCII-умножение, необходимо преобразовать множимое и множитель в "распакованный десятичный" формат, обнулив в каждом байте левые тройки. После умножения необходимо выполнить коррекцию результата с помощью команды AAM. ъ Прежде чем выполнить ASCII-деление, необходимо: 1) преобразовать делимое и делитель в "распакованный десятичный" формат, обнулив в каждом байте левые тройки и 2) выполнить коррекцию делимого с помощью команды AAD. ъ Для большинства арифметических операций используйте преобразование чисел из ASCII-формата в двоичной формат. В процессе такого преобразования проверяйте на корректность ASCII-символы: они должны быть от шест.30 до шест.39, могут содержать десятичную запятую (точку) и, возможно, знак минус. ВОПРОСЫ ДЛЯ САМОПРОВЕРКИ ------------------------------------------------------------ 13.1. Предположим, что регистр AX содержит 9 в ASCII коде, а регистр BX -7 также в ASCII коде. Объясните и дайте точный результат для следующих несвязанных операций: а) ADD AX,33H б) ADD AX,BX AAA AAA в) SUB AX,BX г) SUB AX,0DH AAS AAS 13.2. Поле UNPAK содержит шест. 01040705 в распаковочном десятичном формате. Напишите цикл, который преобразует это содержимое в ASCII-формат, т.е. 31343735. Ассемблер для IBM PC. Глава 13 12 13.3. Поле ASCA содержит значение 313733 в ASCII-формате, а другое поле ASCB содержит 35. Напишите команды для умножения этих чисел в ASCII-формате и записи произведения в поле ASCPRO. 13.4. Используя данные из вопроса 13.3, разделите ASCA на ASCB и запишите частное в поле ASCQUO. 13.5. Выполните следующие вычисления вручную: а) преобразо вать ASCII 46328 в двоичный формат и показать результат в шест.виде; б) преобразовать полученное шест.значение обратно в ASCII-формат. 13.6. Напишите и выполните программу, которая определяет размер памяти компьютера (INT 12H - см.гл.2), преобразует полученное значение в ASCII-формат и выводит результат на экран в следующем виде: Размер памяти nnn байтов. Ассемблер для IBM PC. Глава 14 24 ГЛАВА 14. Обработка таблиц ------------------------------------------------------------ Обработка таблиц Цель: Раскрыть требования для определения таблиц, организа ции поиска в таблицах и сортировки элементов таблицы. ВВЕДЕНИЕ ------------------------------------------------------------ Многие программные применения используют табличную организацию таких данных, как имена, описания, размеры, цены. Определение и использование таблиц включает одну новую команду ассемблера - XLAT. Таким образом, использова ние таблиц - это лишь дело техники и применения знаний, полученных из предыдущих глав. Данная глава начинается определением некоторых общепринятых таблиц. Организация поиска в таблице зависит от способа ее определения. Существует много различных вариантов определения таблиц и алгоритмов поиска. ОПРЕДЕЛЕНИЕ ТАБЛИЦ ------------------------------------------------------------ Для облегчения табличного поиска большинство таблиц определяются систематично, т.е. элементы таблицы имеют одина ковый формат (символьный или числовой), одинаковую длину и восходящую или нисходящую последовательность элементов. Таблица, которой уже приходилось пользоваться в данной книге - это стек, представляющий собой таблицу из 64-х неинициализированных слов: STACK DW 64 DUP(?) Следующие две таблицы инициализированы символьными и числовыми значениями: MONTAB DB 'JAN','FEB','MAR', ... ,'DEC' COSTAB DB 205,208,209,212,215,224,... Таблица MONTAB определяет алфавитные аббревиатуры месяцев, а COSTAB - определяет таблицу номеров служащих. Таблица может также содепжать смешанные данные (регулярно чередующиеся числовые и символьные поля). В следующей ассортиментной таблице каждый числовой элемент (инвентарный номер) имеет две цифры (один байт), а каждый символьный элемент (наименование) имеет девять байтов. Точки, показанные в наименовании "Paper" дополняют длину этого поля до 9 байт. Точки показывают, что недостающее пространство должно присутствовать. Вводить точки необязательно. Ассемблер для IBM PC. Глава 14 25 STOKTBL DB 12,'Computers',14,'Paper....',17,'Diskettes' Для ясности можно закодировать элементы таблицы вертикально: STOKTBL DB 12, 'Computers' DB 14, 'Paper....' DB 17, 'Diskettes' Рассмотрим теперь различные способы использования таблиц в программах. ПРЯМОЙ ТАБЛИЧНЫЙ ДОСТУП ------------------------------------------------------------ Предположим, что пользователь ввел номер месяца - 03 и программа должна преобразовать этот номер в алфавитное значение March. Программа для выполнения такого преобразова ния включает определение таблицы алфавитных названий месяцев, имеющих одинаковую длину. Так как самое длинное название - September, то таблица имеет следующий вид: MONTBL DB 'January..' DB 'February.' DB 'March....' Каждый элемент таблицы имеет длину 9 байт. Адрес элемента 'January' - MONTBL+0, 'February' - MONTBL+9, 'March' - MONTBL+18. Для локализации месяца 03, программа должна выполнить следующее: 1. Преобразовать введенный номер месяца из ASCII 33 в двоичное 03. 2. Вычесть единицу из номера месяца: 03 - 1 = 02 3. Умножить результат на длину элемента (9): 02 х 9 = 18 4. Прибавить произведение (18) к адресу MONTBL; в результате получится адрес требуемого названия месяца: MONTBL+18. ------------------------------------------------------------ ------------------------------------------------------------ Рис. 14.1. Прямая табличная адресация. На рис.14.1 приведен пример прямого доступа к таблице названий месяцев. Для краткости в программе используются вместо девятисимвольных названий - трехсимвольные. Введенный номер месяца определен в поле MONIN. Предположим, что некоторая подпрограмма формирует запрос на ввод номера месяца в ASCII-формате в поле MONIN. Описанная техника работы с таблицей называется прямым табличным доступом. Поскольку данный алглритм непосредствен но вычисляет адpес необходимого элемента в таблице, то в программе не требуется выполнять операции поиска. Ассемблер для IBM PC. Глава 14 26 Хотя прямая табличная адресация очень эффективна, она возможна только при последовательной организации. То есть можно использовать такие таблицы, если элементы располагаются в регулярной последовательности: 1, 2, 3,... или 106, 107, 108,... или даже 5, 10, 15. Однако, не всегда таблицы построены таким образом. В следующем разделе рассматриваются таблицы, имеющие нерегулярную организацию. ТАБЛИЧНЫЙ ПОИСК ------------------------------------------------------------ Некоторые таблицы состоят из чисел, не имеющих видимой закономерности. Характерный пример - таблица инвентарных номеров с последовательными номерами, например, 134, 138, 141, 239 и 245. Другой тип таблиц состоит из распределенных по ранжиру величин, таких как подоходный налог. В следующих разделах рассмотрим эти типы таблиц и организацию табличного поиска. Таблицы с уникальными элементами Инвентарные номера большинства фирм часто не имеют последовательного порядка. Номера, обычно, группируются по категориям, первые цифры указывают на мебель или приборы, или номер отдела. Кроме того время от времени номера удаляются, а новые добавляются. В таблице необходимо связать инвентарные номера и их конкретные наименования (и, если требуется, включить стоимость). Инвернтарные номера и наименования могут быть определены в различных таблицах, например: STOKNOS DB '101','107','109',... STOKDCR DB 'Excavators','Processors','Assemblers',... или в одной таблице, например: STOKTAB DB '101','Excavators' DB '107','Processors' DB '109','Assemblers' ... Программа на рис.14.2 определяет инвентарную таблицу и выполняет табличный поиск. Таблица содержит шесть пар номеров и наименований. Цикл поиска начмнается со сравнения введенного инвентарного номера в поле STOKNIN с первым номером в таблице. Если номера различные, то адрес в таблице увеличивается для сравнения со следующим инвентарным номером. Если номера равны, то программа (A30) выделяет наименование из таблицы и записывает его в поле DESCRN. Поиск выполняет максимум шесть сравнений и если требуемый номер в таблице отсутствует, то происходит переход на программу обработки ошибки, которая выводит на экран соответствующее сообщение. Ассемблер для IBM PC. Глава 14 27 Обратите внимание, что в начале программы имеется команда, которая пересылает содержимое поля STOKNIN в регистр AX. Хотя STOKNIN определенно как 3233, команда MOV загрузит в регистр AX это значение в обратной последователь ности байтов 3332. Так как элементы таблицы имеют прямую последовательность байтов, то после команды MOV имеется команда XCHG, которая меняет местами байты в регистре AX, возвращая им прямую последовательность, т.е. 3233. Команда CMP, предполагая обратную последовательность, сравнивает сначала правые байты, а затем - левые. Следовательно, проверка на pавенство будет корректной, но проверки на больше или меньше дадут неправильные результаты. Для сравнения на больше или меньше следует опустить команду XCHG, переслать элемент таблицы командой MOV, скажем, в регистр BX и затем сравнить содержимое регистров AX и BX следующим образом: MOV AX,STOKNIN LEA SI,STOKTAB C20: MOV BX,[SI] CMP AX,BX JA или JB ... В программе такого типа другая таблица может определять стоимость единицы товара. Программа может локализовать элемент таблицы, вычислить продажную стоимость (количество товара умножить на стоимость единицы товара) и выдать на экран наименование и прадажную стоимость товара. В примере на рис. 14.2 таблица содержит двухбайтовые номера и десятибайтовые наименования. Детальное программиро вание будет oтличаться для различного числа и длины элементов. Например, для сравнения трехбайтовых полей можно использовать команду REPE CMPSB, хотя эта команда также включает использование pегистра CX. Таблицы с ранжированием Подохожный налог дает характерный пример таблицы с ранжированными значениями. Представим себе таблицу, содержащую размеры доходов oблагаемых налогами, процент налога и поправочный коэффициент: Размер дохода Процент налога Поправочный к-нт 0-1000.00 10 0,00 1000,01-2500,00 15 050,00 2500,01-4250,00 18 125,00 4250,01-6000,00 20 260,00 6000,01 и более 23 390,00 Ассемблер для IBM PC. Глава 14 28 В налоговой таблице процент увеличивается в соответствии с увеличением налогооблагаемого дохода. Элементы таблицы доходов содержат максимальные величины для каждого шага: TAXTBL DD 100000,250000,425000,600000,999999 для организации поиска в такой таблице, программа сравнивает доxод налогоплатильщика с табличным значением дохода: если меньше или равно, то использовать соответствующий процент и поправку; если больше, то перейти к следующему элементу таблицы. Величина налога рассчитывается по формуле: Доход х Процент налога : 100 - поправочный к-нт Табличный поиск с использованием сравнения строк Если элемент таблицы превышает длину в два байта, то для операции сравнения можно использовать команду REPE CMPS. Предположим, что таблица инвентарных номеров (рис.14.2) переделана для трехбайтовых номеров. Если STOKNIN является первым полем в области данных, а STOKTAB - вторым, то они могут выглядеть cледующим образом: Данные: |123|035Excavators|038Lifters |049Presses | ... | | | | | | | Адрес: 00 03 06 16 19 29 32 Программа на рис.14.3 определяет таблицу STOKTAB, включая последний элемент '999' для индикации конца таблицы при поиске. Программа поиска сравнивает содержимое каждого элемента таблицы с содержимым поля STOKNIN: Элемент таблицы STOKNIN Результат сравнения 035 123 Меньше: проверить след.эл-т 038 123 Меньше: проверить след.эл-т 049 123 Меньше: проверить след.эл-т 102 123 Меньше: проверить след.эл-т 123 123 Равно: элемент найден Заметим, что команда CMPSB на рис.14.3 сравнивает байт за байтом, пока байты не будут равны и автоматически увеличива ет регистpы SI и DI. ------------------------------------------------------------ ------------------------------------------------------------ Рис.14.3. Табличный поиск с использованием команды CMPSB Ассемблер для IBM PC. Глава 14 29 Регистр CX инициализируется значением 03, а начальные относительные адреса в регистрах SI и DI устанавливаются равными 03 и 00 соответственно. Сравнение с первым элементом таблицы (035:123) завершается на первом байте, после этого регистр SI содержит 04, DI: 01, CX: 02. Для следующего сравнения регистр SI должен иметь значение 16, а DI: 00. Корректировка регистра DI сводится к простой перезагрузке адреса STOKNIN. Увеличение адреса следующего элемента таблицы, который должен быть в регистре SI, зависит от того, на каком байте (первом, втором или третьем) закончилось предыдущее сравнение. Регистр CX содержит число байт, не участвующих в сравнении, в данном случае - 02. Прибавив к содержимому регистра SI значение в регистре CX и длину наименования, получим относительный адрес следующего элемента: Адрес в SI после CMPSB 04 Прибавить CX 02 Прибавить длину наименования 10 Относительный адрес след.элемента 16 Так как регистр CX всегда содержит число байт, не участвующих в сравнении (если такие есть), то расчет справедлив для всех случаев: прекращение сравнения после 1, 2 или 3 байта. Если сравниваются одинаковые элементы, то регистр CX получит значение 00, а адрес в pегистре SI укажет на требуемое наименование. Таблицы с элементами переменной длины Существуют таблицы, в которых элементы имеют переменную длину. Каждый элемент такой таблицы может завершаться специальным символом ограничителем, например, шест.00; конец таблицы можно обозначить огpаничителем шест.FF. В этом случае необходимо гарантировать, чтобы внутри элементов таблицы не встречались указанные ограничители. Помните, что двоичные числа могут выражаться любыми битовыми комбинация ми. Для поиска можно использовать команду SCAS. ТРАНСЛИРУЮЩАЯ КОМАНДА XLAT ------------------------------------------------------------ Команда XLAT транслирует содержимое одного байта в другое предопределенное значение. С помощью команды XLAT можно проверить корректность содержимого элементов данных. При передаче данных между персональным компьютером и ЕС ЭВМ (IBM) с помощью команды XLAT можно выполнить перекодировку данных между форматами ASCII и EBCDIC. В следующем примере происходит преобразование цифр от 0 до 9 из кода ASCII в код EBCDIC. Так как представление цифр в ASCII выглядит как шест.30-39, а в EBCDIC - шест.F0-F9, то замену можно выполнить командой OR. Однако, дополнительно преобразуем все остальные коды ASCII в пробел (шест.40) в Ассемблер для IBM PC. Глава 14 30 коде EBCDIC. Для команды XLAT необходимо определить таблицу перекодировки, которая учитывает все 256 возможных символов, с кодами EBCDIC в ASCII позициях: XLTBL DB 47 DUP(40H) ;Пробелы в коде EBCDIC DB 0F0H,0F1H,0F2H,0F3H,...,0F9H ;0-9 (EBCDIC) DB 199 DUP(40H) ;Пробелы в коде EBCDIC Команда XLAT предполагает адрес таблицы в регистре BX, а транслируемый байт (например, поля ASCNO) в регистре AL. Следующие команды выполняют подготовку и трансляцию байта: LEA BX,XLTBL MOV AL,ASCNO XLAT Команда XLAT использует значение в регистре AL в качестве относительного aдреса в таблице, т.е. складывает адрес в BX и смещение в AL. Если, например, ASCNO содержит 00, то адрес байта в таблице будет XLTBL+00 и команда XLAT заменит 00 на шест.40 из таблицы. Если поле ASCNO cодержит шест.32, то адрес соответствующего байта в таблице будет XLTBL+50. Этот байт содержит шест.F2 (2 в коде EBCDIC), который команда XLAT загружает в регистр AL. ------------------------------------------------------------ ------------------------------------------------------------ Рис.14.4. Преобразование ASCII в EBCDIC. В программе на рис.14.4 добавлено преобразование десятичной точки (2E) и знака минус (2D) из кода ASCII в код EBCDIC (4B и 60 соответственно). В программе организован цикл для обработки шестибайтового поля. Поле ASCNO в начале выполнения программы содержит значение 31.5 с последующим пробелом, или шест.2D33312E3520. В конце выполнения програм мы в поле EBCNO должно быть шест. 60F3F14BF540. ПРОГРАММА: ОТОБРАЖЕНИЕ ШЕСТ. И ASCII-КОДОВ ------------------------------------------------------------ Программа, приведенная на рис.14.5, отображает на экране почти все ASCII-символы, а также их шест.значения. Например, ASCII-символ для шест.53 - это буква S, эти данные программа выводит в виде 53 S. Полное изображение на экране выглядит в виде матрицы 16х16: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ------------------------------------------------------------ Ассемблер для IBM PC. Глава 14 31 ------------------------------------------------------------ Рис.14.5. Отображение шест. и ASCII-кодов Как было показано еще на рис.8.1, отображение ASCII- символов, oсобых проблем не вызывает. Что же касается отображения шест.значений в символах ASCII, то этот процесс более сложный. Например, для вывода на экран в коде ASCII шест. 00, 01 и т.д. необходимо преобразовать шест.00 в шест. 3030, шест.01 в шест.3031 и т.д. В программе начальное значение поля HEXCTR равно 00. Это значение последовательно увеличивается на 1. Процедура C10HEX расщепляет байт HEXCTR на две щест.цифры. Предположим, что байт HEXCTR содержит шест. 4F. Процедура сначала выделяет шест.цифру 4 и использует это значение для перекодировки по таблице XLATAB. В регистре AL устанавливает ся в результате значение шест.34. Затем процедура выделяет вторую шест.цифру F и перекодирует ее в шест.46. В результате oбработки получается шест.3446, что отображается на экране как 4F. Так как функция DOS для вывода на экран (шест.40) рассматривает шест.1A как конец файла, то в программе это значение заменяется на пробел. Программа, использующая для вывода на экран функцию DOS (шест.09), дожна заменять символ ограничитель '$' на пробел. Существует много различных способов преобразования шест.цифр в ASCII-символы. Можно поэкспериментировать с операциями сдвига и сравнения. ПРОГРАММА: СОРТИРОВКА ЭЛЕМЕНТОВ ТАБЛИЦЫ ------------------------------------------------------------ Часто возникает необходимость сортировки элементов таблицы в восходящем или нисходящем порядке. Например, пользователю может потребоваться список наименований товара в алфавитном порядке или список общих цен в нисходящей последовательности. Обычно, табличные данные не определяются как в предыдущей программе, а загружаются с клавиатуры или с диска. Данный раздел посвящен сортировке элементов таблицы, что касается различных применений, включающих сортировку записей на дисках, то здесь возможны более сложные программы. Существует несколько алгоритмов сортировки таблиц от неэффективных, но понятных, до эффективных и непонятных. Программа сортировки, предлагаемая в данном разделе, весьма эффективна и может применяться для большенства табличных сортировок. Конечно, если не проверить различные алгоритмы сортировок, то даже самая неэффективная программа может показаться работающей со скоростью света. Но цель данной книги - показать технику ассемблера, а не сортировки. Основной подход заключается в сравнении соседних элементов таблицы. Если первый элемент больше второго, то элементы меняются местами. Таким образом выполняется сравнение элементов 1 со 2, 2 с 3 и т.д. до конца таблицы с Ассемблер для IBM PC. Глава 14 32 перестановкой элементов там, где это необходимо. Если в проходе были сделаны перестановки, то весь процесс повторяет ся с начала таблицы т.е. сравниваются снова элементы 1-2, 2-3 и т.д. Если в проходе не было перестановок, то таблица отсортирована и можно прекратить процесс. Ниже приведен алгоритм, в котором переменная SWAP является индикатором: была перестановка элементов (YES) или нет (NO): G10: Определить адрес последнего элемента G20: Установить SWAP=NO Определить адрес первого элемента G30: Элемент > следующего элемента? Да: Представить элементы Установить SWAP=YES Перейти к следующему элементу Конец таблицы? Нет: Перейти на G30 Да: SWAP=YES? Да: Перейти на G20 (повторить сорт.) Нет: Конец сортировки Программа, показанная на рис.14.6, обеспечивает ввод с клавиатуры до 30 имен, сортировку введенных имен в алфавит ном порядке и вывод на экран отсортированного списка имен. ------------------------------------------------------------ ------------------------------------------------------------ Рис.14.6. Сортировка таблицы имен. ОПЕРАТОРЫ ТИПА, ДЛИНА И РАЗМЕРА ------------------------------------------------------------ Ассемблер содержит ряд специальных операторов, которые могут оказаться полезными при программировании. Например, при изменении длины таблицы придется модифицировать программу (для нового определения таблицы) и процедуры, проверяющие конец таблицы. В этом случае использование операторов TYPE (тип), LENGTH (длина) и SIZE (размер) позволяют уменьшить число модифицируемых команд. Рассмотрим определение следующей таблицы из десяти слов: TABLEX DW 10 DUP(?) ;Таблица из 10 слов Программа может использовать оператор TYPE для определения типа (DW в данном случае), оператор LENGTH для определения DUP-фактора (10) и оператор SIZE для определения числа байтов (10 х 2 = 20). Следующие команды иллюстрируют три таких применения: MOV AX,TYPE TABLEX ;AX=0002 MOV BX,LENGTH TABLEX ;BX=000A (10) MOV CX,SIZE TABLEX ;CX=0014 (20) Ассемблер для IBM PC. Глава 14 33 Значения LENGTH и SIZE можно использлвать для окончания табличного поиска или сортировки. Например, если регистр SI содержит продвинутый адрес таблицы при осуществлении поиска, то проверка на конец таблицы может быть следующий: CMP SI,SIZE TABLEX В главе 23 "Справочник по директивам ассемблера" дается детальное описание операторов TYPE, LENGTH и SIZE. ОСНОВНЫЕ ПОЛОЖЕНИЯ НА ПАМЯТЬ ------------------------------------------------------------ ъ Для большинства применений, определяйте таблицы, имеющие родственные элементы одной длины и формата данных. ъ Стройте таблицы на основе форматов данных. Например, элементы могут быть символьные или числовые длиной один, два и более байтов каждый. Может оказаться более практичным определение двух таблиц: одна, например, для трехсимвольных значений номеpов, а другая для двухбайтовых значений цен единиц т