Cамоучитель по Assembler

         

Глава 7


Математический сопроцессор 8087


      Конструкторы микропроцессора Intel 8088 предусмотрели для него
    уникальную возможность, характерную лишь для семейства
    микропроцессоров 8086/8088.  Конструкции микропроцессора позволяет
    иметь в системе сопроцессор.  Сопроцессор - это устройство,
    расширяющее возможности центрального процессора.  Арифметический
    сопроцессор 8087 является сопроцессором центрального
    микропроцессора 8088, добавляющий команды числовой обработки и
    регистры с плавающей точкой.  Эти дополнительные арифметические
    возможности расширяют набор команд микропроцессора 8088, и
    значительно увеличивают вычислительную мощность в тех случаях,
    когда программа выполняет операции с плавающей точкой и повышенной
    точности.


 
      Фирма сконструировала персональную ЭВМ так, чтобы была
    возможность установить в нее сопроцессор.  На системной плате рядом
    с панелью микросхемы 8088 существует пустая 40- штырьковая панель.
    Фирма IBM смонтировала эту панель для того, чтобы можно было
    установить любой сопроцессор, архитектурно совместимый с
    микропроцессором 8088.  Арифметический сопроцессор 8087 отвечает
    этим требованиям; он совместим и на уровне архитектуры, и по
    расположению выводов.  Для того, чтобы использовать его
    вычислительные возможности, вам нужно всего лишь вставить
    микросхему 8087 в пустую панель.  Затем нужно установить
    перключатель на системной плате в положение, показывающее что
    сопроцессор существует; это делается только для того, чтобы
    сопроцессор воспринимал от микросхамы 8087 прерывания по особой
    ситуации.  А собственно работа микросхамы 8087 не зависит ни от
    одного из переключателей.

Работа 8087


      Сопроцессор 8087 обрабатывает команды с плавающей точкой,
    контролируя команды, выполняемые процессором 8088.      Арифметический
    сопроцессор "смотрит" на команды, выполняемые процессором 8088.
    Когда сопроцессор 8087 "видит" команду, котрую он должен выполнить,
    он начинает ее обработку.  Микросхема 8087 выполняет свои числовые
    операции параллельно с микропроцессором 8088.  То есть, пока
    микросхема 8087 выполняет арифметическую команду, микропроцессор
    8088 может продолжать выполнять свои команды.  Тем самым возникает
    истинная параллельность работы; микропроцесоор 8088 может выполнять
    команду, пока сопроцессор 8087 выполняет другую команду.  В
    частности, это имеет значение в случае, когда команда сопроцессора
    8087 занимает много времени, что характерно для некоторых команд с
    плавающей точкой.
 
      Поскольку два процессора могут работать параллельно, необходима
    некоторая синхронизация межлу ними.  Эту работу выполняет команда
    WAIT микропроцессора 8088.      Микросхема 8087 подключена к
    микропроцессору 8088 таким образом, что когда процессор числовой
    обработки занят (выполняет команду плавающей точки), вход TEST
    микропроцессора 8088 неактивен.  Команда WAIT останавливает
    обработку команд в микропроцессоре 8088 до тех пор, пока вход TEST
    не станет активен, сигнализуя о завершении команды сопроцессора
    8087.  Таким образом, при работе микропроцессора 8088 можно
    гарантировать, что микросхема 8087 завершит свою работу до того,
    как микропроцессор 8088 выберет на выполнение следующую команду.
    Это также предотвращает выборку данных до завершения исполнения
    команды.
 
      Процессор и сопроцессор связаны только по внешним линиям
    управления, таким как вход TEST.  Микропроцессор 8088 не может
    читать внутренние регистры микросхемы 8087, и наоборот.  Все
    данные, передаваемые между ними, должны быть помещены в память, к
    которой оба процессора имеют доступ.  Но из-за того, что регистры
    адресации находятся в микропроцессоре 8088, микросхеме 8087 трудно
    эффективно адресовать память, используя те же способы адресации,
    которые используют микропроцессор 8088.  Чтобы позволить микросхеме
    8087 адресовать память с помощью способов адресации микропроцессора
    8088, существует взаимодействие двух процессоров при выполнении
    команд плавающей точки.
 
      Набор команд микропроцессора 8088 содержит команду ESC, которая
    сама по себе не выполняется в микропроцессоре 8088.  В системах без
    сопроцессора 8087 команда ESC идентична команде NOP - за
    исключением того что для ее выполнения требуется большн времени.
    Все команды ESC имеют встроенную адресную информацию, а именно, для
    вычисления адреса, они используют байт mod=r/m.  Несмотря на то,
    что команда ESC действует как команда NOP, микропроцессор 8088
    выполняет вычисление исполнительного адреса, а заетм выполняет
    чтение памяти по результирующему адресу, хотя и не производит
    никаких действий с данными.  Если байт mod=r/m определяет регистр,
    а не память микропроцессора 8088, никакого чтения памяти не
    происходит.
 
      Тем временем микросхема 8087 "наблюдает" за последовательностью
    команд, выполняемых микропроцессором 8088.  Когда микропроцессор
    выполняет команду ESC, микросхема 8087 распознает эту команду, как
    свою собственную.  Затем микросхема 8087 ждет, пока микропроцесоор
    8088 выполнить фиктивное чтение памяти.  Когда адрес памяти
    оказывается на системной шине, микросхема 8087 "захватывает" его, и
    начиная с этого момента знает, где находятся данные в памяти, не
    вычисляя при этом адреса.  Микропроцессор 8088 вычисляет адрес, а
    микросхема 8087 выполняет остальную часть команд.  Теперь
    микросхема 8087 может "похищать" некоторые циклы памяти для чтения
    или записи данных, а микропроцесоор 8088 в это время продолжает
    работу.
 
      Микросхема 8087 добавляет арифметические возможности в систему,
    но не замещает ни одну команду микропроцессора 8088.  Команды ADD,
    SUB, MUL и DIV, описанные в гл.4, выполняются микропроцессором
    8088, а арифметический сопро- цессор 8087 выполняет дополнительные,
    более эффективные команды арифметической обработки.  С точки зрения
    программиста, система с установленной в ней микросхемой 8087 должна
    выглядеть, как единый процессор с большим набором команд, чем
    простой микропроцессор 8088.  Лишь в немногих местах надо помнить,
    какой процессор какую команду выполняет.  Только тогда, когда
    микропроцессор 8088 должен непосредственно использовать результат
    работы микросхемы 8087 и требуется синхронизирующая команда WAIT,
    различать процессоры становится необходимо.
 
      Когда вы используете Макроассемблер фирмы IBM, возникает
    проблема при написании программ, использующих набор команд
    микросхемы 8087.  Чтобы использовать эту микросхему, нужно
    сформировать ее команды, используя коды операций WAIT и ESC.
    Наилучший способ сделать это - написать набор макрокоманд, которые
    позволяют писать команды микросхемы 8087.  В гл.6 были приведены
    некоторые макрокоманды, необходимые для написания команд
    сопроцессора 8087.  Если вы собираетесь программировать микросхему
    8087, вам нужно дописать примеры гл.6 для полного набора команд
    сопроцессора 8087.

Типы данных 8087


      Микросхема 8087 имеет расширенный набор типов данных,
    необходимых для поддержки ее расширенных арифметических
    возможностей.  В то время как микропроцессор 8088 может
    непосредственно работать только с байтами и словами, микросхема
    8087 имеет семь типов данных.  Шесть из них присущи лишь микросхеме
    8087.  На Фиг. 7.1 показаны все семь типов данных, с которыми
    работает микросхема 8087.  Четыре формата представляют целые числа,
    а три формата - вещественные, или числа с плавающей точкой.  Один
    формат представляет упакованные десятичные числа.
 
      Рисунок 7.2 иллюстрирует способы, которыми микросхема хранит
    эти числа в памяти.  Как и в случае данных микропроцессора 8088,
    все данные хранятся с младшей частью операнда, записанной в младших
    адресах.  Бит знака всегда оказывается в байте по старшему адресу
    памяти.  Мы будем обсуждать назначение различных полей по мере
    того, как будем рассматривать разные типы данных.
 
      Сопроцессор 8087 работает с тремя типами целых чисел:  словом,
    которое имеет длину 16 бит и идентично слову микропроцессора 8088;
    коротким целым числом, имеющим длину 32 бита; длинным целым числом,
    64-значением.  Все эти числа являются числами, представленными в
    двоичном дополнительном коде.
 
      В программе целое слово определяется с помощью оператора DW.
    Такое целое слово может иметь значение в диапазоне от - 32768 до
    32767.  Этот целый формат уже использовался в наборе команд
    микропроцессора 8088.  Это - единственный формат данных, общий для
    микропроцессора 8088 и арифметического сопроцессора 8087.  Короткий
    целый формат требуется в операторе описания данных длиной 32 бита.
    Такое описание двойного слова выполняет оператор DD, определяющий
    целые числа, лежащие в диапазоне от -232 до 232-1.      Напоминаем, что
    с помощью оператора DD можно также определить пару
    СЕГМЕНТ:СМЕЩЕНИЕ.  Ассемблер решает сам, какую именно форму
    сгенерировать, основываясь на операнде.  Если операнд - адрес,
    порождается пара СЕГМЕНТ:СМЕЩЕНИЕ; если же операнд - просто число,
    соответственно порождается длинное целое число.  Для описания
    длинных 64-битовых целых чисел используется оператор определения
    счетверенного слова DQ.  Эта директива вынуждает ассемблер
    сформировать поле данных, состоящее их четырех слов (восьми байт).
    Такой тип целого может иметь значения в диапазоне от -264 до 264-1.
    Этот оператор ассемблера, так же как и операторы DB, DW и DD, может
    определить константу, неопределенное поле (если задан операнд "?"),
 
                 <ДДДД ВОЗРАСТАНИЕ ЗНАЧЕНИЯ
 
                       ЪДВДДДДДДДДДДДДДДДї
            СЛОВА ЦЕЛЫЕ  іSі  ВЕЛИЧИНА     і(ДВОИЧНОЕ
                       АДБДДДДДДДДДДДДДДДЩ ДОПОЛНЕНИЕ)
                        15           0
 
                       ЪДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
            КОРОТКОЕ ЦЕЛОЕ іSі      ВЕЛИЧИНА                і(ДВОИЧНОЕ
                       АДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ ДОПОЛНЕНИЕ)
                        31                      0
 
                       ЪДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
            ДЛИННОЕ ЦЕЛОЕ  іSі                 ВЕЛИЧИНА                           і
                       АДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
                        63
 
                       ЪДВДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
      УПАКОВАННОЕ ДЕСЯТИЧНОЕ іSі   X   і                   ВЕЛИЧИНА                              і
                       АДБДДДДДДДБd17Бd16Бd15Бd14Бd13Бd12Бd11Бd10Бd9ДБd8ДБd7ДБd6ДБd5ДБd4ДБd3ДБd2ДБd1ДБd0ДЩ
                        79      72                                                   0
 
                       ЪДВДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДї
       КОРОТКОЕ ВЕЩЕСТВЕННОЕ іSіПОРЯДОК   і   МАНТИССА            і
                       АДБДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДЩ
                        31    23               0
 
                       ЪДВДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
      ДЛИННОЕ ВЕЩЕСТВЕННОЕ іSі  ПОРЯДОК    і               МАНТИССА                     і
                       АДБДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
                        63        52                                        0
 
                       ЪДВДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
      ВРЕМЕННОЕ ВЕЩЕСТВЕННОЕ іSі     ПОРЯДОК      ГДДї                  МАНТИССА                   і
                       АДБДДДДДДДДДДДДДДДДДДБДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
                        79          64 63                                             0
 
                КОММЕНТАРИИ:
                S - бит знака (0 - плюс, 1 - минус)
                dn - Десятичная цифра (по две на бит)
                X - Незначащие биты; 8087 их игнорирует при загрузке и обнуляет при сохранении
                  - Позиция подразумеваемой двомичной точки
                I - Целый бит мантиссы; сохраняется для временных действительных, подразумевается - для коротких и длинных
                ПОРЯДОК (нормализованные значения):
                  Короткие действительные: 127 (7FH)
                  Длинные действительные: 1023 (3FFH)
                  Временные действительные: 16383 (3FFFH) 
 
      Фиг. 7.1 Форматы данных 8087 (Copyright Intel 1980)
    а также несколько восьмибайтовых полей с помощью команды DUP.
      Оставшийся целый тип данных - упакованный десятичный формат.
    Этот тип данных представляет целое число в упакованном десяточном
    формате.  Такие данные занимают десять байт.  Один байт
    резервируется для знака, а оставшиеся девять байт содержат 18
    десяточных цифр.  Такое упакованное представление десятичных чисел
    идентично представлению десятичных операндов микропроцессора 8088,
    но при этом представлении одновременно обрабатывается 18 цифр.
    Команды десятичной коррекции упакованных десятичных чисел
    микропроцессора 8088 допускают одновременно только две десятичные
    цифры.  Кроме того, упакованные десятичгые числа микропроцессора
    8088 требуют, чтобы программист определил метод обработки знака
    числа, если используются отрицательные числа.  Упакованные
    десятичные числа сопроцессора 8087 имеют бит знака в старешм байте.
    Упакованное десятичное число хранится в десятичном коде, причем
    старший бит 10-байтного поля содержит знак (0 - положительно, 1 -
    отрицательно).
      Для описания упакованного десяточного числа используется
    оператор определения десятибайтового поля DT.  Чтобы задать
    уракованное десятичное число в этом поле, необходимо использовать
    шестнадцатеричную запись.  Если в поле операнда окажется целое
    число, ассемблер преобразует его в дополнительный двоичный код, а
    не в упакованное десятичное число.    К счастью, преобразовать
    десятичное число в необходимую шестнадцатеричную форму легко.
A
                      ЪДВДВДДДДДДДДДДДї
                      і |M|       і
                    +3іS|S|       і
                      і |B|       і
                      ГДБДБДДДДДДДДДДДґ
                      і           і
                    +2і           і
                      і           і
     ЪДВДВДДДДДДДДДДДї      ГДДДДДДДДДДДДДДДґ            ЪДВДВДДДДДДДДДДДї
     і |M|       і          і           і          і |M|       і
   +1іS|S|       і        +1і           і       +3іS|S|       і
     і |B|       і          і           і          і |E|       і
     ГДБДБДДДДДДДДДВДґ      ГДДДДДДДДДДДДДВДґ            ГДЕДЕДДДДДДДДДДДґ
     і               |Lі          і         |Lі          іL|M|       і
   +0і               |Sі        +0і         |Sі       +2іS|S|       і
     і               |Bі          і         |Bі          іE|F|       і
     АДДДДДДДДДДДДДБДЩ      АДДДДДДДДДДДДДБДЩ            ГДБДБДДДДДДДДДДДґ
     7                0     7          0           і           і
     ЦЕЛЫЕ СЛОВА       КОРОТКИЕ ЦЕЛЫЕ           +1і           і
                                             і           і
                   і    ЪДВДДДДДДДДДДДДДї          ГДДДДДДДДДДДДДВДґ       ЪДВДВДДДДДДДДДДДї
                   і    і |       і          і        |Lі     і |M|       і
                   і  +9іS|    (X)      і       +0і        |Sі  +9іS|S|       і
                   і    і |       і          і        |Fі     і |E|       і
                   і    ГДБДДДДДВДДДДДДДґ          АДДДДДДДДДДДДДБДЩ       ГДБДБДДДДДДДДДВДґ
                   і    і         |     і          7          0      і        |Lі
                   Р  +8і         |     і          КОРОТКИЕ ВЕЩЕСТВ.    +8і        |Sі
                   О    і         |     і                            і        |Eі
     ЪДВДВДДДДДДДДДДДї С    ГДДДДДДДЕДДДДДДДґ            ЪДВДВДДДДДДДДДДДї       ГДВДВДДДДДДДДДБДґ
     і |M|       і Т    і         |     і          і |M|       і і         і |M|       і
   +7іS|S|       і        +7і     |     і       +7іS|S|       і і      +7іI|S|       і
     і |B|       і А    і         |     і          і |E|       і і         і |F|       і
     ГДБДБДДДДДДДДДДДґ Д    ГДДДДДДДЕДДДДДДДґ            ГДБДБДДВДВДВДДДДґ і     ГДБДБДДДДДДДДДДДґ
     і                 і Р    і         |     і          і    |L|M|    і і       і           і
   +6і                 і Е  +6і         |     і       +6і    |S|S|    і і    +6і           і
     і                 і С    і         |     і          і    |E|F|    і Р       і           і
     ГДДДДДДДДДДДДДДДґ О    ГДДДДДДДЕДДДДДДДґ            ГДДДДДДБДБДБДДДДґ О     ГДДДДДДДДДДДДДДДґ
     і                 і В    і         |     і          і           і С         і           і
   +5і                 і        +5і     |     і       +5і           і Т      +5і           і
     і                 і          і     |     і          і           і     і           і
     ГДДДДДДДДДДДДДДДґ      ГДДДДДДДЕДДДДДДДґ            ГДДДДДДДДДДДДДДДґ А     ГДДДДДДДДДДДДДДДґ
     і                 і          і     |     і          і           і Д         і           і
   +4і                 і        +4і     |     і       +4і           і Р      +4і           і
     і                 і          і     |     і          і           і Е         і           і
     ГДДДДДДДДДДДДДДДґ      ГДДДДДДДЕДДДДДДДґ            ГДДДДДДДДДДДДДДДґ С     ГДДДДДДДДДДДДДДДґ
     і                 і          і     |     і          і           і О         і           і
   +3і                 і        +3і     |     і       +3і           і В      +3і           і
     і                 і          і     |     і          і           і     і           і
     ГДДДДДДДДДДДДДДДґ      ГДДДДДДДЕДДДДДДДґ            ГДДДДДДДДДДДДДДДґ       ГДДДДДДДДДДДДДВДґ
     і                 і          і     |     і          і           і     і        | і
   +2і                 і        +2і     |     і       +2і           і  +2і        | і
     і                 і          і     |     і          і           і     і        | і
     ГДДДДДДДДДДДДДДДґ      ГДДДДДДДЕДДДДДДДґ            ГДДДДДДДДДДДДДДДґ       ГДДДДДДДДДДДДДДДґ
     і                 і          і     |     і          і           і     і           і
   +1і                 і        +1і     |     і       +1і           і  +1і           і
     і                 і          і     |     і          і           і     і           і
     ГДДДДДДДДДДДДДВДґ      ГДДДДДДДЕДДДДДДДґ            ГДДДДДДДДДДДДДВДґ      ГДДДДДДДДДДДДДВДґ
     і               |Lі          і     |     і          і        |Lі     і        |Lі
   +0і               |Sі        +0і     |     і       +0і        |Sі  +0і        |Sі
     і               |Bі          і     |     і          і        |Fі     і        |Fі
     АДДДДДДДДДДДДДБДЩ      АДДДДДДДБДДДДДДДЩ            АДДДДДДДДДДДДДБДЩ       АДДДДДДДДДДДДДБДЩ
     7                0     7          0            7          0     7          0
     ДЛИННЫЕ ЦЕЛЫЕ          УПАКОВАННЫЕ ДЕС.              ДЛИННЫЕ ВЕЩЕСТВ.       ВРЕМЕННЫЕ ВЕЩЕСТВ.
 
       S: Бит знака                                   S: Бит знака
       MSB/LSB: Старший/младший бит                   MSE/LSE: Старший/младший бит порядка
       MSD/LSD: Старшая/младшая десятичная цифра            MSF/LSF: Старший/младший бит дробной части
       (X): Биты не имеют значения                    I: Целый бит мантиссы
A
           Фиг. 7.2 Структура хранения данных в 8087

            Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:03:56
            Фиг. 7.3 Целочисленные форматы данных сопроцессора 8087       Page         1-1
 
 
                                          PAGE    ,132
                                          TITLE   Фиг. 7.3 Целочисленные форматы данных сопроцессора 8087
 
             0000                   CODE    SEGMENT
 
             0000  04D2             WORD        DW      1234
            7_3.ASM(6): warning A4016: Reserved word used as symbol: WORD
             0002  FB2E                         DW      -1234
 
             0004  40E20100               SHORT_INTEGER   DD      123456
             0008  C01DFEFF                           DD      -123456
 
             000C  D202964900000000       LONG_INTEGER    DQ      1234567890
             0014  2EFD69B6FFFFFFFF                   DQ      -1234567890
             001C  ????????????????                   DQ      ?
             0024  78563412907856341200     PACKED_BCD      DT      00123456789012345678H
             002E  78563412907856341280               DT      80123456789012345678H ; Отрицательное от предыдущего
             0038  0002[                              DT      2 DUP (?)
                   ???????????????
                   ?????
                               ]
 
             004C                   CODE    ENDS
                                          END
 
          Фиг. 7.3 Целочисленные форматы данных сопроцессора 8087
 
    Просто запишите нужное число в десятичной форме, а затем добавьте
    букву H, показывающую, что это - шестнадцатеричное число.
    Изобразить отрицательное число труднее.  Если вы напишите перед
    десятичным числом знак "-", ассемблер преобразует его в двоичный
    дополнительный код, даже если есть буква H.  Поэтому в этом случае
    нужно подсчитать десятичные цифры и удлинить число до 20 цифр.
    первые две цифры должны быть 80, чтобы показать, что число
    отрицательно.  То есть, чтобы изобразить -1234 в упакованном
    десятичном формате, надо записать:
 
      DT 80000000000000001234H
 
      На Фиг. 7.3 изображен листинг ассемблера, иллюстрирующий
    сформированные ассемблером значения в случае четырех типов целых
    чисел.

Представление данных с плавающей точкой


      Для изображения чисел с плавающей точкой в процессоре 8087
    существует три формата данных.  Два из них совпадают с предложенным
    ИИЭР стандартом для таких чисел.  Короткий формат имеет 32 бита, а
    длинный - 64 бита.  Третий формат определяет 80-битовые числа, и не
    совпадает со стандартом ИИЭР.  Сопроцессор 8087 использует такой
    формат "промежуточного действительного числа", чтобы обеспечить
    очень высокую точность для промежуточных результатов вычислений.
 
      Оставшаяся часть этого раздела предназначена для тех, кто не
    имел дела с вещественными числами в ЭВМ.  Вы можете пропустить этот
    раздел, если понимаете о общих чертах способы представления
    вещественных чисел - как на бумаге, так и в ЭВМ; а в следующем
    разделе мы рассмотрим конкретный способ, используемый в
    сопроцессоре 8087.
 
      Целые числа - лучший способ представления многих величин.
    Целые чила просто понимать и использовать, а также легко
    преобразовывать в двоичное представление.  Однако с целыми числами
    плохо работать в случае очень больших значений.  Очень большое
    целое число обычно оканчивается длинной строкой нулей.  Например:
    Солнце находится на расстоянии около 93000000 миль от Земли.  Целые
    числа, кроме того, не способны представить значение, содержащее
    дробную часть, то есть ЭВМ не может запомнить число 1/2 в целом
    представлении.  Любые другие дроби, меньшие 1, также невозможно
    представить, используя целые числа.
 
      Ученые и математики давным-давно разработали способ
    представления этих чисел в достаточно удобном виде.  На первом
    этапе вводится десяточная точка.  Этот символ, "." показывает
    границу между целой и дробной частью числа.  В случае целого числа
    позиция, представляющая единицы, всегда находится на рпавом краю
    числа; в случае, когда используется десятичная точка, цифры справа
    от нее представляют значения, меньшие единицы.
 
      У целых чисел каждая позиция числа соответствует степени 10.
    То есть число 1234 есть
 
      1234=1000+200+30+4=1*10**3+2*10**2+3*10**1+4*10**0
 
      Десятичная точка показывает границу между позицией,
    соответствующей 100, и дробной частью.  В дробной части позиции
    снова являются сиепенями 10, но теперь эти степени 10 -
    отрицательные.  Поэтому число 1.234 есть
 
      1.234=1+0.2+0.03+0.004=
         1*10**0+2*10**-1+3*10**-2+4*10**-3
 
      Десятичная точка позволяет записывать дроби.  Число 1/2 теперь
    выглядит как 1.5, а 1/4 выглядит как 0.25, и 1/5 выглядит как 0.2.
 
      Поскольку каждая позиция десятичного числа отличается от
    соседней на степень 10, умножение числа на 10 эквивалентно сдвигу
    десятичной точки на одну позицию вправо.  Аналогично деление на 10
    сдвигает десятичную точку на позицию влево.  Это свойсто можно
    использовать для сдвига десятичной точки на соответствующее место:
    мы сдвигаем десятичную точку и одновременно корректируем число на
    же степень 10.  Такое представление чисел называется представление
    "с плавающей точкой", поскольку десятичная точка "плавает" в числе;
    она больше не помечает абсолютное место между целой и дробной
    частями.  Положение десятичной точки можно выбрать из соображений
    удобства, а затем умножить число на нужную степень 10, чтобы
    получить правильное значение.
 
      Например, Солнце находится на расстоянии примерно 93000000 миль
    от Земли.  С такой формой записи тяжело работать из-за обилия
    нулей.  Можно записать это число в формате с плавающей точкой:
    9.3*107.  То есть 93000000 эквивалентно 9.3, умноженному на 10 в
    седьмой степени.  Фактически,
 
      93000000=9.3*10**7=93*10**6=930*10**5
 
      и так далее.  Мы можем двигать десятичную точку куда угодно,
    меняя степень 10.
 
      Десятичное число с плавающей точкой состоит из двух частей.
    Значащая часть числа называется мантиссой.  В предыдущем примере
    число 9.3 есть мантисса.  На практике мантисса обычно находится в
    пределах 1<=мантисса<10; т.е.  она может быть равна 1.3, 7.6 или
    9.97.  Другая часть числа с плавающей точкой - это порядок,
    степень, в которую нужно возвести 10 перед умножением его на
    мантиссу.  То есть 9.3*107 имеет мантиссу 9.3 и порядок 7.    Если
    основание системы счисления определено раз и навсегда, в нашем
    случае это 10, то для восстановления первоначального числа должны
    быть заданы только два числа - мантисса и порядок.
 
      Представление с плавающей точкой позволяет записявать в
    компактном виде как очень большие (например, 1.234*1085) так и
    очень малые (1.234*10-85) числа.  Чтобы записать те же числа без
    использования степени десяти, потребовались бы длинные строки,
    состоящие из нулей.
 
      Двоичные числа с плавающей точкой изображаются аналогично
    десятичным; отличие заключается в том, что основание системы
    счисления здесь 2, а не 10.  Мантисса имеет значение 1<=мантисса<2,
    а порядок показывает степень 2.  То есть число 1.101*10100 в
    двоичной форме означает, что мантисса 1.101 умножается на 24, или
    16.  Значение мантиссы определяется таким же позиционным способом,
    как и для десятичного числа, за исключением того, что основание
    теперь равно 2.  Позиции, находящиеся справа от двоичной точки,
    представляют отрицательные степени двойки.  Таблица на Фиг. 7.4
    показывает значения первых пяти позиций.
 
      Теперь мы можем вычислить десятичное значение числа из примера:
 
      1.101b=1+1/2+1/8=15/8=1.625
      10**100b=2**4=16
      1.101*10**100B = (1+5/8)*16 = 26
      Кроме того, мы могли бы вычислить это значение точно так же,
    как в случае десятичных чисел.  Значение порядка показывает, на
    сколько позиций надо сдвинуть двоичную точку.  В этом случае,
    поскольку значение порядка равно 4, двоичную точку надо сдвинуть на
    четыре позиции вправо.  Поэтому
 
      1.101*10**100B=11010B=26
 
      Оба метода правильные и дают идентичные результаты.  В первом
    случае вычисляется мантисса, затем она умножается на основание,
    возведенное в степень порядка; во втором случае сначала выполняется
    умножение, а затем вычисляется мантисса.
 
      Двоичное значение   Десятичное значение
     -------------------------------------------
      0.1               1/2
      0.01              1/4
      0.001             1/8
      0.0001                  1/16
      0.00001           1/32
     ----------------------------------------- Фиг. 7.4 Отрицательные
                                        степени двух
 
      Как же эти числа представляются в ЭВМ?    Место, отводимое для
    числа с плавающей точкой, делится на два поля.  Одно поле содержит
    знак и значение мантиссы, а другое содержит знак и значение
    порядка.  Размер поля мантиссы определяет точность представления
    числа.  Чем большее число бит отводится в ЭВМ под поле мантиссы,
    тем выше точность.  Например, десятичная мантисса 1.234 более
    точная, чем мантисса 1.2.  Две дополнительные цифры мантиссы дают
    возможность более точно передавать значение числа.
 
      Для того, чтобы сохранить максимальную точность, вычислительные
    машины почти всегда хранят мантиссу в нормализованном виде.  Это
    означает, что мантисса есть число, лежащее между 1 и 2
    (1<=мантисса<2).  Два соображения говорят в пользу нормализации.
    Во-первых, ни один незначащий нуль не дает никакого вклада в
    точность числа.  (Это несправедливо для нулей, лежащих в конце
    числа; мы считаем, что число 1.000 более точно, чем число 1.0).
    Если в мантиссе с плавающей точкой появились незначащие нули,
    точность числа падает.  Во-вторых, способ хранения мантиссы с
    плавающей точкой подразумевает, что двоичная точка находится на
    фиксированном месте.  Фактически подразумевается, что двоичная
    точка следует после первой двоичной цифры.  То есть нормализация
    мантиссы делает единичным первый бит, помещая тем самым значение
    мантиссы между 1 и 2.  Для выполнения нормализации ЭВМ корректирует
    порядок числа на соответствующее значение.
 
      При нормализации встречаются исключения.  Наиболее очквидное
    исключение - нуль.  В этом случае вся мантисса нулевая.  Второе
    исключение не так очевидно, и возникает в случае приближения числа
    к нижней границе своего диапазона.    Давайте разберемся, что это
    означает.
      Точно так же, как длина мантиссы числа определяет его точность,
    длина поля порядка определяет диапазон числа.  Поле порядка
    содержит степень двойки, и чем больше бит в этом поле, тем большее
    число может быть представлено.
 
      Если под поле порядка (представленного в дополнительном коде)
    отведено три двоичных цмфры, наибольшее представимое число есть
    1.111 ...  * 10011B.  Мантисса - это число, чуть меньше двух, а
    множитель мантиссы равен 23, или 8.  Поэтому максимальное число
    чуть меньше 16.  Наименьшее ненулевое положительное число есть
    1.000 ...  * 10100B, или 1*2-4, или 1/16.  Поскольку в числах с
    плавающей точкой для представления мантиссы не используется
    дополнительный код, диапазон отрицательных чисел такой же.    В
    случае 3-битового порядка диапазон положительных чисел от 1/16 до
    16, а отрицательных - от -16 до -1/16.
 
      Если для поля порядка используется 4 бита, наибольшее число
    равно 1.111...  *100111B<2*27=256.    Наименьшее ненулевое
    положительное число 1.000...  *101000B=1*2-8=1/256.  Таким образом
    четыре бита порядка допускают диапазон изменения чисел от 1/256 до
    256.  Чем большее число бит в поле порядка, тем шире диапазон
    представимых чисел.
 
      Важно отметить, что хотя диапазон и расширяется с увеличением
    числа бит порядка, точность не увеличивается.  В только что
    рассмотренных примерах предполагалось, что имеется 4 бита мантиссы.
    Если выбрать конкретное число, попадающее в диапазон обоих
    примеров, то оно будут иметь одну и ту же точность независимо от
    диапазона порядка.  Например, число 1.010*10001B=21/2.  Увеличение
    количества бит порядка не увеличивает точность числа.  Размер
    мантиссы есть единственное ограничение точности данного числа в
    пределах системы.
 
      Значение порядка хранится не как число, представленное в
    дополнительном коде.  Для упрощения вычислений значение порядка в
    ЭВМ хранится в виде смещенного числа.  Это означает, что к
    действительному значению порядка прибавляется смещение перед
    записью его в память.  Смещенным значение порядка делается для
    того, чтобы можно было сравнивать значения порядка с помощью
    обычной операции сравнения чисел с фиксированной точкой без знака.
    В частности, это полезно при сравнении двух чисел с плавающей
    точкой.  Значения порядка и мантиссы записываются в одном элементе
    данных, причем порядок записывается перед мантиссой.  В случае
    смещенного значения порыдка программа может сравнивать числа
    побитно, начиная со старших позиций.  Первое же неравенство
    показывает соотношение чисел, и больше не нужно учитывать никакие
    части чисел.  Значение смещения определяется размером поля порядка.
    Давайте это рассмотрим на примере форматов данных сопроцессора
    8087.
 
      В сопроцессоре 8087 формат короткого действительного числа
    использует 32 бита, восемь из которых содержат значение порядка;
    таким образом, оно может находиться в пределах от - 128 до 127.
    Однако значение -128 зарезервировано для обозначения некоторого
    специального, а именно, неопределенного числа, так что значения
    порядка находятся в диапазоне от -127 до 127.  Значение порядка -
    это не число, представленное в дополнительном коде.  Сопроцессор
    8087 прибавляет значение смещения 127 к значению порядка
    действительного числа перед тем, как его запомнить.
 
      Значение порядка  Хранимое значение
      ---------------------------------------
       -127             000H
         -1             07EH
          0             07FH
          1             080H
        127             0FEH
      ------------------------------------- Фиг. 7.5 Хранение порядка
 
      В таблице на Фиг. 7.5 показаны некоторые возможные значения
    порядка и его запоминаемое значение.  Как видно из рисунка,
    смещение меняет содержимое поля порядка так, чтобы наименьшему
    значению порядка соответствовало наименьшее действительное число;
    аналогично, наибольшему значению порядка соответствовало наибольшее
    число.  Если два числа с плавающей точкой различаются значением
    порядка, простое сравнение полей порядка упорядочивает оюа числа.
    Поскольку такое сравнение можно продолжать и дальше с полями
    мантисс, программа может легко сравнить два числа с плавающей
    точкой.  И вся эта простота - только благодаря смещению порядка.

Форматы действительных чисел


      Существует три формата чисел с плавающей точкой, поддерживаемых
    сопроцессором 8087.  Эти форматы показаны нп Фиг. 7.1 и 7.2.  На
    Фиг. 7.1 показана логическая структура чисел, а на Фиг. 7.2 показано
    расположение отдельных частей числа, когда оно записывается в
    память.
 
      Короткие и длинные форматы данных соответствуют предложенному
    ИИЭР стандарту для представления чисел с плавающей точкой.
    Короткое действительное число занимает 32 бита, или четыре байта
    памяти.  Этот формат часто называют числом с плавающей тоской
    обычной точности.  Его мантисса содержит 23 бита, что по точности
    приблизительно соответствует шести - семи десятичным цифрам.  То
    есть семизначное десятичное число по точности примерно
    соответствует 23-битовому двоичному числу.  Восьмибитовое поле
    порыдка имеет значение смещения 127, или 07FH.  Значение порядка
    лежит в диапазоне от -127 до 127, что приблизительно соответствует
    диапазону от 10**-38 до 10**38.  Оставшийся бит определяет знак
    всего числа.  Заметим, что внутри чисоа с плавающей точкой есть два
    знаковых бита.  Один из них - знак порядка, содержащийся внутри
    поля порядка (модифицирующийся смещением).  Другой знак показывает,
    что отрицательно или положительно само число.
 
      Длинный действительный формат числа занимает 64 бита, или
    восемь байтов памяти.  Это число удвоенной точности имет 52-
    битовую мантиссу, что по точности соответствует примеоно 15- 16
    десятичным цифрам.  Одиннадцатибитовый порядок имеет диапазон от
    2**-1023 до 2**1023 и значение смещения 1023, или 03FFH.  В десятичной
    форме это соответствует диапазону от 10**-307 до 10**307.
      Сопроцессор 8087 всегда хранит в памяти длинные и короткие
    действительные числа в нормализованном виде.  В это означает, что
    первый бит мантиссы всегда единичен; и поэтому хранить его
    бессмысленно, но всегда подразумевается, что он присутствует, как
    показано на Фиг. 7.2.  Под изображением расположения данных длинных
    и коротких действительных чисел показана 1 перед двоичной точкой.
    Этот разряд отсутствует в памяти, но известно, что он на самом деле
    существует.
 
      Заметим теперь, что все шесть форматов данных сопроцессора
    8087, которые были рассмотрены - четыре целых и два действительных
    - существуют только за пределами процессора.  То есть сопроцессор
    8087 переводит данные в эти форматы только тогда, когда записывает
    их в память; кроме того, он может читать данные, записанные в
    память в этих форматах.  В сопроцессоре 8087 данные всех типов
    хранятся в седьмом формате - промежуточном, или временном
    действительном.  Это означает, что программа, в которой вы
    работаете с сопроцессором 8087, использует преимущества
    расширенного диапазона и точности временного действительного числа
    только тогда, когда данные обрабатываются внутри процессора 8086.
    Другие форматы данных существуют для удобства их хранения и
    представления вне процессора.
 
      Временное действительное число является наиболее точным и имеет
    большой диапазон.  Оно представляется 80 битами или 10 байтами.
    Его мантисса имеет длину 64 бита, это дает точность, эквивалентную
    примерно 19 десятичным цифрам.  Мантисса в общем случае
    нормализована, но в некоторых случаях может оказаться
    денормализованной.  По этой причине временный действительный формат
    уже не пожразумевает, что старший бит мантиссы равен 1.  На Фиг. 7.2
    старшая нормализованная единица показана явно частью мантиссы, а не
    чем-то подразумеваемым.  Пятнадцатибитовое поле порядка смещается
    значением 16383, или 03FFFH.  Такой порядок допускает диапазон
    изменения чисел от 2**-16383 до 2**16383, или примерно от 10**-4932 до
    10**4932.  Поскольку сопроцессор 8087 может денормализовать мантиссу
    временного действительного числа, нижняя граница его расширяется
    еще дальше.  Так как число денормализовано, в мантиссе есть
    незначащие нули, и это позволяет изобразить даже меньшее число, чем
    допускает диапазон значений порядка.  Например, давайте снова
    рассмотрим простую форму числа с плавающей точкой, содержащую
    3-битовое поле порядка и 4-битовую матиссу.  Наименьшее
    положительное число, которое здесь может быть представлено, есть
 
      1.000*10000B=1*2**-3=1/8
 
      В этом примере подразумевается:  что значение порядка смещено
    на значение 3.  Если теперь мя денормализуем мантиссу, то сможем
    представить даже меньшее число, например
 
      0.100*10000B=1/2*2**-3=1/16
 
      а наименьшее положительное число, которое можно представить
    денормализацией мантиссы есть

      0.001*10000B=1/8*2**-3=1/64
 
      Денормализация мантиссы расширяет нижнюю границу диапазона
    чисел.
 
      Этот дополнительный диапазон дается не даром.  Поскольку мы
    ввели незначащие нули, точность мантиссы уменьшилась.  Временный
    действительный формат числа дает возможность пожертвовать точностью
    ради расширения диапазона числа, когда это необходимо.  Такая
    возможность требуется в первую очередь на промежуточных шагах
    длительных вычислений.  Иногда прикладная программа вычитает два
    примерно одинаковых числа перед выполнением другой операции, и
    результат много меньше, чем любое из исходных чисел.  Эта разность
    может оказаться очень важной.  В таких специальных случаях
    вычисления можно продолжать, допуская расширение диапазона
    представления числа.  Единственной альтернативой здесь была бы
    установка нулевого результата вычитания, а в этом случае всякая
    значимость теряется.

Определение действительных чисел


      Ассемблер отдельно порождает поля данных дествительных чисел с
    помощью четырех-, восьми- и десятибайтовых описаний.  Короткому
    действительному числу здесь соответствует оператор DD; длинному
    действительному числу соответствует оператор DQ; а временному
    действительному чисоу - оператор DT.
 
      К сожалению, Макроассемблер фирмы IBM не обслуживает числа с
    плавающей точкой процессора 8087.  Если вы укажите действительное
    число в качестве операнда оператора DD или DQ, то в качестве данных
    получите запутанный набор нулей и единиц.  Этот формат плавающих
    чисел соответствует формату, используемому внутри интерпретатора
    языка Бейсик, и не является форматом ИИЭР, используемым в
    сопроцессоре 8087.  Поэтому программа для сопроцессора 8087 должна
    использовать несколько иной метод порождения действительных чисел.
 
      Существует два метода, которые можно использовать для
    построения действительных чисел.  Первый метод - ручное
    ассемблирование.  Вы выписываете действительное число в праивльном
    формате с помощью бумаги и ручки.  Эта операция содержит:
    тщательный анализ числа, преобразование десятичного числа в
    двоичную форму, разделение порядка и мантиссы, смещение порядка, и
    наконец, преобразование результата в двоичную или шестнадцатеричную
    систему.  Это реальный метод, но он требует изрядного опыта и
    огромных затрат времени программиста.  Второй метод порожления
    действительных чисел - косвенный метод, требующий большого
    количества машинного времени, но гораздо меньших затрат времени
    программиста.  Здесь вы записываете действительное число в виде
    произведения ил частного двух или более целых.  Ассемблер может
    непосредственно порождать целые числа с помощью команд DW, DD или
    DQ.  Сразу же после инициализации микросхемы 8087 программа может
    построить необходимые ей действительные числа, выполняя необходимые
    умножения и деления.  Например, в программе нужна константа с
    плавающей точкой 1.234*105.  Сформировать такую константу в целом
    представлении программист сможет легко, используя следующий оператор
      DD 123400
 
      Заметим, что число слишком велико для представления оператором
    DW, но удовлетворяет ограничениям 32-битовых целых чисел.
 
      Но предположим, что нужно построить число 1.234*10**-5, которое
    очень мало.  В этом случае в программе потребуется два целых числа:
    одно целое число соответствует значению мантиссы, а другое
    соответствует десяти в степени порядка.  Можно воспользоваться
    следующими определениями
 
      DW   1234
      DD   100000000
 
      чтобы построить необходимое число.  Программист делит дробную
    часть числа 1234 на значение 10**8 и получает нужное число.
 
      При использовании такого подхода возникают сложности в работе с
    очень большими или малыми числами.    Эти числа требуют представления
    степеней десяти, которые по значению много больше любых
    представимых целых чисел.  Далее приводится пример, который строит
    действительный формат каждой третьей степени десяти и может помочь
    в таком случае.  Отложим рассмотрение этой программы до тех пор,
    пока не изучим необходимые для нее команды.  С помощью таких
    действителтных представлений степеней 10 записать числа от 1036 до
    10-24 будет достаточно легко.  Программа может разбить число на
    целую часть и степень десяти, а умножение и деление дадут верное
    значение.  Микросхема 8087 также допускает и другие способы
    выполнения таких операций масштабирования, но методы, используюшие
    целые числа и степени 10, наиболее просты, особенно для людей,
    работающих с сопроцессором 8087 впервые.

Регистровый стек


      Сопроцессор 8087 имеет четыре регистра специального назначенмя
    и восьмирегистровый стек для арифметических операндов.  Этот
    регистровый стек лежит в основе работы сопроцессора 8087, и поэтому
    мы прежде всего рассмотрим именно его.
      Регистровый стек процессора числовой обработки состоит из
    восьми позиций, каждая из которых имеет ширину 80 бит.  Сороцессор
    8087 запоминает все объекты во временном действительном формате,
    независимо от того, как они представлены в памяти системы;
     ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
     і                 ПОЛЕ ДАННЫХ                  ПОЛЕ    і
     і      79   78        64 63                0  1   0 і
     іЪДДДДДДВДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДї  ЪДДДДДДїі
     ііЗНАК  і ПОРЯДОК       і         МАНТИССА       і  і    іі
     іГДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ  ГДДДДДДґі
     іі      і               і                        і  і    іі
     іГДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ  ГДДДДДДґі
     іі      і               і                        і  і    іі
     іГДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ  ГДДДДДДґі
     іі      і               і                        і  і    іі
     іГДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ  ГДДДДДДґі
     іі      і               і                        і  і    іі
     іГДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ  ГДДДДДДґі
     іі      і               і                        і  і    іі
     іГДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ  ГДДДДДДґі
     іі      і               і                        і  і    іі
     іГДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ  ГДДДДДДґі
     іі      і               і                        і  і    іі
     іАДДДДДДБДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ  АДДДДДДЩі
     і                      15                     0            і
     і                      ЪДДДДДДДДДДДДДДДДДДДДДДї            і
     і                      іУПРАВЛЯЮЩИЙ РЕГИСТР   і            і
     і                      ГДДДДДДДДДДДДДДДДДДДДДДґ            і
     і                      іРЕГИСТР СОСТОЯНИЯ     і            і
     і                      ГДДДДДДДДДДДДДДДДДДДДДДґ            і
     і                      і                і            і
     і                      ГД УКАЗАТЕЛЬ  КОМАНД  Дґ            і
     і                      і                і            і
     і                      ГДДДДДДДДДДДДДДДДДДДДДДґ            і
     і                      і                і            і
     і                      ГД УКАЗАТЕЛЬ  ДАННЫХ ДДґ            і
     і                      і                і            і
     і                      АДДДДДДДДДДДДДДДДДДДДДДЩ            і
     АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
 
        Фиг. 7.6 Регистровая труктура микросхемы INTEL 8087
 
    сопроцессор 8087 преобразует целые и действительные числа во
    внутренний формат.  Как мы уже видели, такое представление
    допускает очень высокую точность и большой диапазон.  В случае
    целых чисел сопроцессор 8087 получает точные результаты вплоть до
    значения 2**64.  Внутренний формат с плавающей точкой малоинтересен
    для тех программистов, которые обрабатывают целые числа с помощью
    микросхемы 8087.
 
      Регистры сопроцессора 8087 функционируют как обычный стек,
    такой же, как и стек микропроцессора 8088.  Но у этого стека
    имеется ограниченное число позиций - только вомесь.  Процессор 8087
    имеет еще один регистр, труднодоступный для программиста.  Этот
    регистр представляет собой слово, содержащее "этикетки" каждой
    позиции стека.  Такой регистр позволяет сопроцессору 8087
    отслеживать, какие из позиций стека используются, а какие свободны.
    Любая попытка помещения объекта в стек на уже занятую позицию
    приводит к врзникновению особой ситуации - недействительной
    операции.  Ниже будут обсуждены особые ситуации, и то, как
    программа может бороться с проблемой переполнения стека.
 
      Программа может заносить данные в стек сопроцессора 8087 с
    помощью команды загрузки, и все команды загрузки помешают данные в
    вершину стека.  Если число в главной памяти записано не во
    временном действительном формате, микросхема 8087 преобразует его в
    80-битовое представление во время выполнения команды загрузки.
    Аналогично команды записи извлекают значение из стека микросхемы
    8087 и помещают их в главную память, а если необходимо
    преобразование формата данных, сопроцессор 8087 выполняет его как
    часть операции записи.  Некоторые формы операции записи оставляют
    вершину стека нетронутой для дальнейших действий.
 
      После того, как программа поместила данные в стек сопроцессора
    8087, они могут быть использованы в любой вычислительной команде.
    Вычислительные команды процессора 8087 допускают как действия между
    регистрами, так и действия между памятью и регистрами.  Точно так
    же, как и в случае микропроцессора 8088, из любых двух операндов
    арифметической операции один должен находиться в регистре.    У
    сопроцессора 8087 один из операндов должен быть всегда верхним
    элементом стека, а другой операнд может быть взят их памяти, либо
    из стека регистров.  Стек регистров сопроцессора 8087 всегда должен
    быть приемником результата любой арифметической операции.
    Непосредственно записать результат в память той же командой,
    которая выполнила вычисления, процессор числовой обработки не
    может.  Чтобы переслать операнд обратно в память, необходима
    отдельная команда записи (или команда извлечения из стека с записью
    в память).    Некоторые арифметические команды извлекают верхний
    элемент стека и сбрасывают его, не записывая в память.

Управляющее слово


      Сопроцессор 8087 содержит два управляющих регистра.  Один
    регистр нужен для записи управляющего слова, другой - для
    считывания слова состояния.  Управляющий регистр позволяет
    программисту задать режим рабоиы микросхемы 8087, далее о
    возможностях некоторых из них будет лишь упомянуто; рассомтрение
    всех возможных вариаций управляющего слова выходит за пределя этой
    книги.  Структура управляющего регистра показана на Фиг. 7.7.
      Управляющее слово позволяет выбрать один из двух различных
    типов бесконечности.  По умолчанию способом замыкания числовой
    системы является проективное замыкание, при котором сопроцессор
    8087 трактует положительную, и отрицательную бесконечности как
    единую бесконечность без знака.  Другой способ - аффинное
    замыкание, в которое входят и положительная, и отрицательная
    бесконечности.  Хотя может показаться, что проективное замыкание
    теряет информацию, оно никогда не приводит к бессмысленным
    результатам.  Аффинное замыкание в программе необходимо
    использовать только тогда, когда неоюходима дополнительная
    информация и когда программа написана для того, чтобы иметь дело с
    (возможно) неверными результатами.
 
      Сопроцессор 8087 позволяет выбрать один из четырех способов
    округления чисел.  Округление возникает тогда, когда результат
       15                       7                     0
      ЪДДДДДДДДДДДВДДДВДДДДДДДВДДДДДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДДї
      і         іIC і  RC     і  PC іIEMі іPM іUM іOM іZM іDM іIM  і
      АДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДДЩ
       ДДДДДВДДДДД  і  ДДДВДДД  ДДВДД   і   і   і   і   і   і   і    і МАСКИ ИСКЛЮЧЕНИЯ (1=МАСКИРУЕТСЯ)
            і       і     і       і   і   і   і   і   і   і   і    АД  НЕВЕРНАЯ ОПРЕАЦИЯ
            і       і     і       і   і   і   і   і   і   і   АДДДДДД  НЕНОРМАЛИЗОВАННЫЙ ОПЕРАНД
            і       і     і       і   і   і   і   і   і   АДДДДДДДДДД  ДЕЛЕНИЕ НА НОЛЬ
            і       і     і       і   і   і   і   і   АДДДДДДДДДДДДДД  ПЕРЕПОЛНЕНИЕ
            і       і     і       і   і   і   і   АДДДДДДДДДДДДДДДДДД  ИСЧЕЗАНИЕ ПОРЯДКА
            і       і     і       і   і   і   АДДДДДДДДДДДДДДДДДДДДДД  ТОЧНОСТЬ
            і       і     і       і   і   АДДДДДДДДДДДДДДДДДДДДДДДДДД(ЗАРЕЗЕРВИРОВАНО)
            і       і     і       і   АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДМАСКА РАЗБЛОКИРОВКИ ПРЕРЫВАНИЙ (1)
            і       і     і       АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДУПРАВЛЕНИЕ ТОЧНОСТЬЮ (2)
            і       і     АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДУПРАВЛЕНИЕ ОКРУГЛЕНИЕМ (3)
            і       АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДУПРАВЛЕНИЕ НЕОПРЕДЕЛЕННОСТЬЮ (4)
            АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД(ЗАРЕЗЕРВИРОВАНО)
 
                           (1) Маска разблоктровки прерываний:
                              0 = Разблокировка
                              1 = Прерывания заблокированы (маскированы)
                           (2) Управление точностью:
                              00 = 24 бита
                              01 = (зарезервировано)
                              10 = 53 бита
                              11 = 64 бита
                           (3) Управление округлением:
                              00 = Округление до ближайшего или четного
                              01 = Округление в меньшую сторону
                              10 = Округление в болюшую сторону
                              11 = Обрезание к нулю
                           (4) Управление неопределенностью:
                              0 = Прожективная
                              1 = Афинитивная
 
          Фиг. 7.7 Формат управляющего слова INTEL 8087
 
    требует большей точности, чем позволяет представление чисел.  Метод
    округления определяет, какое число выбирается в качестве
    результата.  Методы округления включают:  выбор следующего большего
    числа в системе представления чисел, следующего меньшего числа,
    округление в направлении нуля, и округление в направлении четного
    числа.  Сопроцессор 8087 также дает возможность программе не
    использовать полностью 64-битовую точность временного
    действительного формата ИИЭР.  Эта возможность не уменьшает время
    выполнения команд и не должна выполняться только для того, чтобы
    сделать команды процессора 8087 совместимыми с некоторыми
    существовавшими ранее процедурами обработки.
 
      Процессор 8087 может регистрировать множество ситуаций
    появления ошибки, известных как особые ситуации; он также может
    возбуждать прерывания, чтобы сообщить об этих ситуациях.  В
    управляющем слове имеется группа бит, которая позволяет
    программисту определить, какие особые ситуации будут приводить к
    прерываниям, а какие будут обрабатываться другими способами.  Эти
    биты известны как маски прерываний, так как они могут маскировать,
    или предотвращать, возникновение прерываний.
 
      Необходимости разрешать прерывание в каждой из этих ситуаций
    нет.  Сопроцессор 8087 имеет широкие возможности по обработке
    особых ситуаций, встроенных в него самого.  Для любой возможной
    ситуации в микросхеме 8087 имеется способ обработки ее по
    умолчанию.    Например, деление на нуль вызывает прерывание по особой
    ситуации, если она разрешена (немаскирована).  Однако если
    программист не разрешил это прерывание, сопроцессор 8087 использует
    значение бесконечности, как результат деления на нуль.  Это
    значение распространится через все оставшиеся вычисления, давая
    результат, непосредственно показывающий, что во время работы
    возникла ошибка некоторого вида.
 
      Иногда в какой-то из ваших программ, использующих плавающую
    точку, может оказаться необходимым обрабатывать особую ситуацию
    сразу же, как она возникает; и тогда у вас возникает желание
    заменить обработчик прерываний вашей собственной подпрограммой,
    обрабатывающей особые ситуации.  В IBM PC вывод прерывания по
    особой ситуации от микросхемы 8087 подключен ко входу
    немаскируемого прерывания NMI (Non Maskable Interrupt).  Это то же
    самое прерывание, которое сигнализирует об ошибке четности.
 
      В качестве примера особой ситуации рассмотрим упомянутое выше
    переполнение стека.  Если программа помещает в стек девятый объект,
    сопроцессор 8087 отвечает прерыванием по особой ситуации.  Если это
    прерывание не разрешено, сопроцессор 8087 отмечает операцию как
    недействительную и дает результат со специальным значением,
    известным как NAN (Not A Number, не число).  Если вы собираетесь
    выполнять сложные вычисления, требующие более восьми позиций стека,
    то можете выгодно использовать прерывание по этой особой ситуации.
    Когда возникает переполнение стека, обработчик этого прерывания
    может удалить нижние элементы стека, используя команды записи.
    Затем программа освобождает эти позиции стека для дальнейшего
    использования.  Существует также и соответствующее прерывание по
    исчерпыванию стека, которое возникает при использовании пустой
    позиции стека; подпрограмма обработки прерывания может обрабатывать
    и эту ситуацию:  она может восстановить прежнее состояние стека из
    области сохранения.
 
      Далее мы увидим, что микросхема 8087 содержит информацию о
    своем состоянии, что делает возможным обработать особые ситуации в
    обработчике прерываний.  Микросхема 8087 имеет исчерпывающую
    информацию о команде, которая вызвала особую ситуацию.

Слово состояния


      Слово состояния микросхемы 8087 содержит текущее состояние
    процессора.  Расположение отдельных битов слова состояния показано
    на Фиг. 7.8.  В слове состояния имеются биты, показывающие особые
    ситуации, так что обработчик прерывания сможет определить суть
    возникшей ситуации.  Также в слове состояния имеется бит,
    показывающий, занят сопроцессор или нет.  Это тот же самый бит,
    который выведен наружу для синхронизации с микропроцессором 8088.
    Слово состояния содержит и указатель на текущую вершину стека
    внутри набора регистров процессора 8087.

       15                       7                     0
      ЪДДДВДДДВДДДДДДДДДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДї
      і B іC3 і    ST     іC2 і C1іC0 іIR і     іPE іUE іOE іZE іDE іIE і
      АДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ
        і   і  ДДДДДВДДДДД ДДДДДВДДДДД  і   і   і   і   і   і   і    і ФЛАГИ ИСКЛЮЧИТЕЛЬНЫХ СОСТОЯНИЙ (1=ЕСТЬ)
        і   і       і     і     і   і   і   і   і   і   і    АД  НЕВЕРНАЯ ОПЕРАЦИЯ
        і   і       і     і     і   і   і   і   і   і   АДДДДДД  НЕНОРМАЛИЗОВАННЫЙ ОПЕРАНД
        і   і       і     і     і   і   і   і   і   АДДДДДДДДДД  ДЕЛЕНИЕ НА НОЛЬ
        і   і       і     і     і   і   і   і   АДДДДДДДДДДДДДД  ПЕРЕПОЛНЕНИЕ
        і   і       і     і     і   і   і   АДДДДДДДДДДДДДДДДДД  ИСЧЕЗАНИЕ ПОРЯДКА
        і   і       і     і     і   і   АДДДДДДДДДДДДДДДДДДДДДД  ТОЧНОСТЬ
        і   і       і     і     і   АДДДДДДДДДДДДДДДДДДДДДДДДДД(ЗАРЕЗЕРВИРОВАНО)
        і   і       і     і     АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЗАПРОС НА ПРЕРЫВАНИЕ
        і   АДДДДДДДЕДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДСОД УСЛОВИЯ (1)
        і         АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДУКАЗАТЕЛЬ ВЕРШИНЫ СТЕКА (2)
        АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЗАНЯТО
 
            (1) См. описание команды сравнения, тестирования, опроса, и остатка в разделе
                  S.7 для прерывания по коду состояния
            (2) Значения ST:
                  000 = Вершина стека - регистр 0
                  001 = Вершина стека - регистр 1
                  *
                  *
                  111 = Вершина стека - регистр 7
 
           Фиг. 7.8 Формат слова состояния INTEL 8087
 
      Вероятно, наиболее используемая часть слова состояния
    микросхемы 8087 - это поле кода условия.  В слове состояния есть
    четыре бита, которые могут устанавливаться командами сопроцессора
    8087.  Два из этих битов кода условия непосредственно соответствуют
    флагам переноса и нуля микропроцессора 8088; фактически, они
    расположены на тех же позициях в старшем байте слова состояния
    процессора 8087.  Преимущества такого расположения этих бит можно
    использовать, записав слово состояния в память; затем загрузить
    старший байт слова состояния в регистр AH, и с помощью команды SAHF
    устанавливаете флаги переноса и нуля в соответствии с результатом
    операции сравнения в сопроцессоре 8087.  Так как все числа в
    сопроцессоре 8087 - действительные со знаком, этих двух флагов
    достаточно для сравнения любых двух чисел.  Далее мы будем иметь
    дело с примерами, которые используют слово состояния для сравнения
    чисел.  Оставшиеся два бита регистра кода условия используются
    специальной командой процессора 8087 для проверки появления любого
    из чисел, соответствующих специальным условиям, которые известны
    сопроцессору 8087.  Поскольку многие из этих чисел требуют
    индивидуальных способов обработки, регистр кода условия
    предоставляет способ распознавания таких чисел.

Набор команд сопроцессора 8087


      Команды сопроцессора 8087 должны рассматриваться как расширение
    набора команд микропроцессора 8088, т.е.  микросхема 8087 добавляет
    команды к общему набору.  Адресация памяти выполняется так же, как
    и в микропроцессоре 8088.  Как мы уже видели ранее, так происходит
    потому, что в действительности микропроцессор 8088 сам порождает
    адреса памяти, а сопроцессор 8087 только выполняет требуемые
    опреции над данными.
 
      Сороцессор 8087 имеет набор регистров для чисел с плавающей
    точкой, организованный в виде стека.  Вершину стека микросхема 8087
    локализует с помощью 3=битового указателя, содержащегося в слове
    состояния.    Прочитав слово состояния, вы сможете узнать, какой из
    восьми регистров является текущей вершиной стека; но, как будет
    видно далее, эта информация в программе требуется редко.
 
      Вся адресация регистрового стека микросхемы 8087 делается
    относительно текущей вершины стека.  Мнемонически на ассемблере
    вершина стека называется ST(0) или просто ST.  Элемент, лежащий
    прямо под вершиной стека, называется ST(1), второй элемент - ST(2),
    и так далее, вплоть до последнего элемента, ST(7).      Для простоты
    использования мы будем опускать скобки в обозначениях элементов
    стека, используя ST0, ST1, ST2 и так далее.
 
      А теперь давайте посмотрим, как программа может добраться к
    элементам стека.  Команда
 
      FADD   ST0, ST3
 
      складывает вершину стека и четвертый его элемент, помещая
    результат в вершину стека.      На Фиг. 7.9а показаны действия этой
    команды.  Ссылка на регистры ST0 и ST3 жестко связана с указателем
    стека.  На рисунке складываются значения A и D, а результат A + D
    замещает значение A, так как оно лежит в вершине стека.  На
    Фиг. 7.9б до выполнения той же самой команды в стек был помещен
    элемент E.    Команда по=прежнему складывает содержимое вершины стека
    с его четвертым элементом; но, как показано на Фиг. 7.9б, она теперь
    складывает значения E и C, замещая результатом E + C зеачение E.  В
    результате помещения элемента E в стек перераспределения операндов
    внутри сопроцессора 8087 не произошло, но произошло их перемещение
    по отношению к вершине стека.  То есть элемент ST3 всегда является
    четвертым элементом стека, независимо от того, где расположен
    указатель стека.
      Мы можем разбить команды сопроцессора 8087 на три широкие
    группы.  В первой группе находятся пересылки данных - загрузки и
    записи данных в сопроцессор 8087 и из него.  Вторая группа -
    управление сопроцессором, команды, которые служат для нужд
    внутреннего сервиса 8087.  В третьей группе команд сосредоточены
    все возможности сопроцессора 8087, это команды числовой обработки.
    Далее мы рассмотрим каждую группу детальнее.  Этот текст не
    содержит исчерпывающего описания команд микросхемы 8087.  Мы будем
    использовать примеры, чтобы с их помощью сообщить достаточное
    количество информации о значении той или иной команды, а
    рассмотрение всех вычислительных возможностей сопроцессора 8087
    выходит за рамки данной книги.
       ЪДДДДДДДДДДДДДДДДДДДДДї                ЪДДДДДДДДДДДДДДДДДДДДДї
       і               і                і               і
       і  ЪДДДДДДДДДДДДї     і                і  ЪДДДДДДДДДДДДї     і
       АДДґ A     ГДДДД +              АДДґ      E     ГДДДД +
        ГДДДДДДДДДДДДґ     і               ГДДДДДДДДДДДДґ     і
        і   B     і     і                 і      A     і     і
        ГДДДДДДДДДДДДґ     і               ГДДДДДДДДДДДДґ     і
        і   C     і     і                 і      B     і     і
        ГДДДДДДДДДДДДґ     і               ГДДДДДДДДДДДДґ     і
        і   D     ГДДДДДЩ                 і      C     ГДДДДДЩ
        ГДДДДДДДДДДДДґ               ГДДДДДДДДДДДДґ
        і          і                 і      D     і
                                     ГДДДДДДДДДДДДґ
                                     і        і
            (a)                          (b)
 
                  Фиг. 7.9 Действие FADD ST0,ST3

Команды пересылки данных


      В группе команд пересылки данных сопроцессора 8087 имеется
    всего три основных команды.  Команда загрузки помещает данные в
    регистровый стек 8087.  Обычно эти данные читаются из памяти
    системы, но команда загрузки может также извлечь число из самого
    стека и заменить им вершину стека.    Команда записи берет данные из
    вершины стека и помещает их в память ЭВМ.  Команда замены
    обменивает два числа в регистровом стеке сопроцессора 8087.
      На Фиг. 7.10 показан листинг ассемблера команд пересылки
    данных.  В текст в самом начале помещен набор макрокоманд
    процессора 8087 с помощью фрагмента:
 
      IF1
      INCLUDE  87MAC.LIB
      ENDIF
 
      Эта последовательность команд помещает в текст программы
    макрокоманды определения команд сопроцессора 8087 во время первого
    прохода ассемблера, когда должны обрабатываться макрорасширения.
    Ассемблер не читает файл макрокоманд во время второго прохода, так
    как этот файл больше не нужен.  В листинге ассемблера появляется
    только команда ENDIF.
      Первая команда пересылки данных, которую мы рассмотрим -
    команда загрузки.  Название всех команд сопроцессора 8087
    начинается с буквы "F".  Так что, чтобы загрузить число в
    микросхему 8087, используется команда FLD (Floating LoaD, плавающая
    загрузка).    В отличие от команд микропроцессора 8088, где команда
    MOV обслуживает все форматы данных, здесь существует разная
           Microsoft (R) Macro Assembler Version 5.00                1/1/80 01:21:45
             Фиг. 7.10 Команды пересылки сопроцессора 8087           Page     1-1
 
                                           PAGE    ,132
                                           TITLE   Фиг. 7.10 Команды пересылки сопроцессора 8087
            0000                     CODE    SEGMENT
                                           ASSUME  CS:CODE,DS:CODE
 
            0000                     WORD_INTEGER    LABEL   WORD
            0000                     SHORT_INTEGER   LABEL   DWORD
            0000                     LONG_INTEGER    LABEL   QWORD
            0000                     BCD_INTEGER     LABEL   TBYTE
            0000                     SHORT_REAL      LABEL   DWORD
            0000                     LONG_REAL       LABEL   QWORD
            0000                     TEMPORARY_REAL  LABEL   TBYTE
 
            0000  9B DF 06 0000 R                FILD    WORD_INTEGER
            0005  9B DB 06 0000 R                FILD    SHORT_INTEGER
            000A  9B DF 2E 0000 R                FILD    LONG_INTEGER
            000F  9B DF 26 0000 R                FBLD    BCD_INTEGER
            0014  9B D9 06 0000 R                FLD     SHORT_REAL
            0019  9B DD 06 0000 R                FLD     LONG_REAL
            001E  9B DB 2E 0000 R                FLD     TEMPORARY_REAL
 
            0023  9B D9 C2                       FLD     ST(2)
 
            0026  9B DF 16 0000 R                FIST    WORD_INTEGER
            002B  9B DB 16 0000 R                FIST    SHORT_INTEGER
            0030  9B D9 16 0000 R                FST     SHORT_REAL
            0035  9B DD 16 0000 R                FST     LONG_REAL
 
            003A  9B DD D2                       FST     ST(2)
 
            003D  9B DF 1E 0000 R                FISTP   WORD_INTEGER
            0042  9B DB 1E 0000 R                FISTP   SHORT_INTEGER
            0047  9B DF 3E 0000 R                FISTP   LONG_INTEGER
            004C  9B DF 36 0000 R                FBSTP   BCD_INTEGER
            0051  9B D9 1E 0000 R                FSTP    SHORT_REAL
            0056  9B DD 1E 0000 R                FSTP    LONG_REAL
            005B  9B DB 3E 0000 R                FSTP    TEMPORARY_REAL
 
            0060  9B DD DA                       FSTP    ST(2)
            0063  9B D9 CA                       FXCH    ST(2)
 
            0066  9B D9 EE                       FLDZ
            0069  9B D9 E8                       FLD1
            006C  9B D9 EB                       FLDPI
            006F  9B D9 E9                       FLDL2T
            0072  9B D9 EA                       FLDL2E
            0075  9B D9 EC                       FLDLG2
            0078  9B D9 ED                       FLDLN2
 
            007B                     CODE    ENDS
                                           END
          Фиг. 7.10 Команды пересылки сопроцессора 8087
    мнемоника для разных типов данных.    Так получилось потому, что
    ассемблер может различать четырехбайтовые и восьмибайтовые
    операнды, но не может знать, является ли операнд действительным или
    целым числом.
 
      Всякий раз, когда операнд - целое, используется команда FILD.
    Итак, FILD загружает слово (16 бит), короткое целое число (32 бита)
    или длинное целое число (64 бита).    Чтобы загрузить упакованное
    десятичное число (80 бит), используется команда FBLD.  Буква B
    указывает десятичные числа.  Наконец, команда FLD загружает
    действительные числа.  Ассемблер определяет, какой вид целого или
    действительного числа вы желаете использовать.
 
      В ассемблере для имен команд сопроцессора 8087, ссылающихся к
    памяти, используется соглашение о том, что в случае целых чисел
    вслед за буквой F следует буква I, в случае десятичных чисел -
    буква B, и никакой буквы не следует в случае действительных чисел.
    Мы увидим, что то же соглашение используется и в командах записи, и
    в арифметических командах, которые указывают операнд в памяти.
 
      Как видно из Фиг. 7.10, для каждого из семи обслуживаемых
    сопроцессором 8087 типов данных существует команда загрузки.
    Команда загрузки указывает поле данных в памяти, микросхема 8087
    преобразует данные из их внешнего представления во временный
    действительный формат.  Преобразованное число помещается в стек,
    увеличивая его объем на единицу.  Если вы попытаетесь поместить
    число в стек, который уже содержит восемь чисел, сопроцессор 8087
    сообщит об особой ситуации - переполнении стека.  Если программа не
    обрабатывает особую ситуацию в своей собственной подпрограмме,
    встроенный обработчик особой ситуации пометит загруженное значение,
    как "неопределенное".  Это означает, что дальнейшие действия с этим
    числом дадут неопределенные результаты.  Если вы сделаете ошибку,
    сопроцессор 8087 проследит за тем, чтобы она не осталась
    незамеченной.
 
      Оставшаяся разновидность команды загрузки берет один из
    элементов стека и помещает его в стек.  Например, команда
 
      FLD    ST0
 
      дублирует вершину стека.  После нее два верхних элемента имеют
    одинаковые значения.  Команда
 
      FLD    ST3
 
      помещает копию четвертого элемента стека в стек.  Заметим, что
    число, которое было раньше ST3, стало теперь ST4.
 
      Давайте посмотрим на машинный язык, в действительности
    формируемый этими командами.  Поскольку этот текст порождает
    команды процессора 8087 с помощью макрокоманд, сравнительно легко
    увидеть, откуда появляются различные части команд.      Во=первых,
    каждая команда начинается с байта 09BH.  Это - команда WAIT.  Как
    вы помните, сопроцессор 8087 должен быть синхронизирован с работой
    микропроцессора 8088.  Если микропроцессор 8088 попытается
    выполнить следующую команду сопроцессора 8087 до того, как
    сопроцессор 8087 завершит текущую команду, действия микросхемы 8087
    дадут неверный результат.      Фактически все макрокоманды 8087
    содержат команду WAIT для обеспечения синхронизации.  (Команды без
    синхронизации сопроцессора 8087 - это все команды управления,
    обычно не требеющие ожидания результата.  Эти команды можно легко
    отличить так как они все начинаются с FN, где буква N означает
    отсутствие синхронизации).
 
      По макрорасширениям также можно видеть, что команды процессора
    8087 формируются командами ESC.  Чтобы указать адрес памяти,
    команда ESC имеет два операнда.  Первый определяет, какая это
    команда ESC, а второй ссылается на ячейку памяти.  Команда ESC
    может иметь длину два, три или четыре байта, в зависимости от
    размера поля индексного смещения, сопровождающего байт mod=r/m.  В
    комбинации с командой WAIT максимальная длина команды сопроцессора
    8087 достигает пяти байт.
 
      Команда записи имеет два варианта.  Первый вариант этой команды
    извлекает число с вершины стека и записывает ее в поименованную
    ячейку памяти.  Выполняя эту команду, сопроцессор 8087 делает
    преобразование данных из временного действительного формата в
    желаемую внешнюю форму.  Эта команда имеет коды операций FST и
    FIST.  (Заметим, что здесь продолжают свое действие соглашения об
    именах команд).  Этой же командой вы можете занести вершину стека в
    любое место внутри стека.
 
      Вероятно вы заметили, что команда FST не допускает запись всех
    возможных внешних типов данных.  Допустимы лишь типы из "большой
    четверки" - целое слово, короткое целое, короткое и длинное
    действительные.  Эта команда не поддерживает все внешниие типы
    данных, потому что создатели процессора 8087 понимали, что это не
    обязательно из=за свойств следующей команды.
 
      Второй вариант команды записи, кроме записи данных, также
    изменяет положение указателя стека.  Команды FSTP (а также команды
    FISTP и FBSTP) выполняют ту же операцию записи данных из
    сопроцессора 8087 в память, но они также извлекают число из стека.
    Эта разновидность команд поддерживает все внешние типы данных.
    Конструкторы микросхемы 8087 кое=где экономили на командах, и
    поэтому только команды FLD и FSTP поддерживают все внешние типы
    данных.  Все остальные команды сохранения данных в памяти работают
    только с "большой четверкой" типов данных.  Конструкторы понимали,
    что эти четыре типа будут преобладать над всеми, и использование
    других форматов может быть реализовано только командами FLD и FSTP.
 
      Команда замены FXCH - следующая команда в группе команд
    пересылки данных.  Команда FXCH меняет местами содержимое вершины
    стека с содержимым другого регистра стека.  Эта команда может
    использовать в качестве операнда только другой элемент стека.
    Нельзя одной командой поменять местами содержимое вершины стека и
    ячейки памяти.  Эта процедура потребует несколько команд и рабочее
    поле где=то в памяти.  В отличие от микропроцесоора 8088,
    сопроцессор 8087 может в одной команде выполнить чтение из памяти
    или запись в память, но не то или другое одновременно.
      Команда     Константа
      --------------------------
        FLDZ            0
        FLD1            1
        FLDPI     PI
        FLDL2T    LOG2(10)
        FLDL2E    LOG2(e)
        FLDG2     LOG10(2)
        FLDLN2    LOGe(2)
      -------------------------- Фиг. 7.11 Константы 8087
 
      Остальные команды группы команд персылки данных обслуживают
    константы.    Они загружают в стек заранее известные значения.  Эти
    константы описывают набор величин, необходимых программам при
    вычислениях, и были выбраны из соображений упрощения счета
    трансцендентных и тригонометрических функций.  Мы используем
    некоторые из этих констант в демонстрационных программах.  Таблица
    на Фиг. 7.11 показывает, какое именно значение загружается в элемент
    ST0 в случае каждой команды.  В каждом случае мнемоника команды
    выбрана так, чтобы отражать значение константы.
 

             Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:04:07
             Фиг. 7.12 Команды управления сопроцессора 8087          Page     1-1
 
                                           PAGE    ,132
                                           TITLE   Фиг. 7.12 Команды управления сопроцессора 8087
            0000                     CODE    SEGMENT
                                           ASSUME  CS:CODE
 
            0000                     STATUS_WORD     LABEL   WORD
            0000                     CONTROL_WORD    LABEL   WORD
            0000                     ENVIRONMENT     LABEL   BYTE        ; Область размером 14 байт
            0000                     STATE             LABEL   BYTE            ; Область размером 94 байта
 
            0000  9B DB E3                       FINIT
            0003  9B DB E0                       FENI
            0006  9B DB E1                       FDISI
            0009  9B 2E: D9 2E 0000 R            FLDCW   CONTROL_WORD
            000F  9B 2E: D9 3E 0000 R            FSTCW   CONTROL_WORD
            0015  9B DB E2                       FCLEX
            0018  9B 2E: D9 36 0000 R            FSTENV  ENVIRONMENT
            001E  9B 2E: D9 26 0000 R            FLDENV  ENVIRONMENT
            0024  9B 2E: DD 36 0000 R            FSAVE   STATE
            002A  9B 2E: DD 26 0000 R            FRSTOR  STATE
            0030  9B D9 F7                       FINCSTP
            0033  9B D9 F6                       FDECSTP
            0036  9B DD C2                       FFREE   ST(2)
            0039  9B D9 D0                       FNOP
            003C  9B                       FWAIT
            003D                     CODE    ENDS
                                           END
 
             Фиг. 7.12 Команды управления сопроцессора 8087

Команды управления


      Команды управления микросхемы 8087 ничего не вычисляют, однако
    они необходимы для управления ее работой.  На Фиг. 7.12 показан
    литсинг ассемблера команд управления сопроцессора 8087.
 
      Многие из команд управления могут выполняться без синхронизации
    с микропроцессором 8088.  На Фиг. 7.12 показаны эти команды в
    форме, ассемблированной с использованием команды WAIT, а код
    операции в поле комментариев изображает команды без команды WAIT.
    Ассемблеру сообщается о том, что операция выполняется без ожидания,
    с помощью символов FN в качестве первых двух символов мнемоники
    команды.
 
      На Фиг. 7.13 перечислены действия всех команд управления
    сопроцессором 8087.  Команды FENI, FDISI, FCLEX обслуживают особые
    ситуации в сопроцессоре 8087.  В управляющем регистре содержится
    маска прерываний, которая выделяет те ситуации, которые могут
    вызвать прерывание.  Маской прерываний сопроцессора 8087 в целом
    управляют команды FENI и FDISI; эти команды аналогичны
    соответственно командам STI и CLI микропроцессора 8088, за
    исключением того, что они управляют прерываниями только от
    сопроцессора 8087.  Команда FCLEX сбрасывает биты особых ситуаций
    регистра состояния.  Микросхема 8087 помнит все особые ситуации,
    так что если последовательность команд возбудила более одного типа
    ошибки, все эти ошибки будут отмечены в регистре состояния.  И
    команда FCLEX - единственный способ сброса этих флагов.
 
      Команда                 Действие
      --------------------------------------------------------
      FINIT Инициализация 8087. Переустановка программ
      FENI  Освобождение прерываний по исключительным состояниям
      FDISI Блокирование прерываний по исключительным состояниям
      FLDCW Загрузка управляющего регистра 8087 из памяти
      FSTCW Сохранение управляющего регистра 8087 в память
      FSTSW Сохранение регистра состояния 8087 в память
      FCLEX Очистка индикаторов исключительных состояний
      FSTENV      Сохранить оборудование 8087 в память
      FLDENV      Загрузить оборудование 8087 из памяти
      FSAVE Сохранить состояние 8087 в память
      FRSTOR      Загрузить состояние 8087 из памяти
      FINCSTP Увеличеть указатель вершины стека
      FDECSTP Уменьшить указатель вершины стека
      FFREE Освободить регистр стека
      FNOP  Ничего не делать
      FWAIT Идентично WAIT
      --------------------------------------------------------
 
      Фиг. 7.13 Управляющие действия
 
      Мы уже рассмотрели управляющее слово и слово состояния в
    составе программной модели сопроцессора 8087.  Команды управления
    FLDCW, FSTCW и FSTSW загружают и сохраняют эти регистры.
      Рабочая среда сопроцессора 8087 содержит все регистры
    микросхемы, за исключением стека данных; рабочая среда состоит из
    14 байт.  Рисунок 7.14 показывает структуру рабочей среды после
    того, как сопроцессор 8087 записал ее в память.  Запись рабочей
    среды - это обычное действие при обработке особой ситуации в
    сопроцессоре 8087, так как рабочая среда содержит все данные об
    особых ситуациях.  Один 20=битовый адрес указывает на последнюю
    команду, которую выполнил сопроцессор 8087.  Другой адрес указывает
    последнюю из вызывавшихся ячеек данных.  Код последней
    выполнявшейся сопроцессором 8087 команды тоже входит в рабочую
    среду.
 
                                      ВОЗРАСТАНИ АДРЕСОВ
                              ДДДДДДДДДДДДДДДДДДДДДї
                15                           0       і
                ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї          і
                і   УПРАВЛЯЮЩЕЕ СЛОВО         і+0      і
                ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ          і
                і   СЛОВО СОСТОЯНИЯ     і+2      і
                ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ          і
                і    СЛОВО ПРИЗНАКА     і+4      і
                ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ          і
                і       IP15-0                і+6
       УКАЗАТЕЛЬ  ГДДДДДДДДДДДВДВДДДДДДДДДДДДДДДДДґ
       КОМАНДЫ    і IP19-16   і0і КОД ОПЕРАЦИИ    і+8
                ГДДДДДДДДДДДБДБДДДДДДДДДДДДДДДДДґ
                і       OP15-0                і+10
       УКАЗАТЕЛЬ  ГДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДґ
       ОПЕРАНДА   і OP19-16   і       0       і+12
                АДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДЩ
 
                  Фиг. 7.14 Рабочая среда 8087
 
      Состояние микросхемы 8087 - это рабочая среда вместе с
    регистрами данных.  Так как в сопроцессоре 8087 имеется восемь
    10=байтовых регистров, состояние содержит 94 байта.  Рисунок 7.15
    иллюстрирует структуру состояния сопроцессора 8087, записанного в
    память.  Структура состояния идентична рабочей среде с добавленным
    в конце регистровым стеком.  Программа может записать состояние
    сопроцессора 8087, когда происходит переключение задач, или если в
    обработчике прерываний потребуется использование сопроцессора 8087.
    Когда ранее выполнявшаяся задача снова получает управление,
    состояние может быть программно восстановлено.
 
      Две команды - FINCSTP и FDECSTP - взаимодействуют с указателем
    стека; они перемещают его.      Все данные в регистрах остаются на
    местах, т.е.  слово "этикеток" не изменяется.  Увеличение указателя
    стека не эквивалентно извлечению данных из стека, так как
    "этикетка" для этих данных все же показывает, что в регистре есть
    данные.  Попытка загрузить какое=либо число в такой стек приведет к
    появлению особой ситуации - переполнению стека.  Команда FFREE
    освобождает ячейку стека, устанавливая "этикетку" для этой ячейки
    так, что она показывает отсутствие данных в ячейке.  Но команда
    FFREE не меняет указатель стека, и если вам просто нужно выбросить
    верхний элемент из стека, то в большинстве случаев это легче
    сделать с помощью арифметической команды.
A
                                      ВОЗРАСТАНИЕ АДРЕСОВ
                              ДДДДДДДДДДДДДДДДДДДДДї
                15                           0       і
                ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї          і
                і   УПРАВЛЯЮЩЕЕ СЛОВО         і+0      і
                ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ          і
                і   СЛОВО СОСТОЯНИЯ     і+2      і
                ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ          і
                і    СЛОВО ПРИЗНАКА     і+4      і
                ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ          і
                і       IP15-0                і+6
       УКАЗАТЕЛЬ  ГДДДДДДДДДДДВДВДДДДДДДДДДДДДДДДДґ
       КОМАНДЫ    і IP19-16   і0і КОД ОПЕРАЦИИ    і+8
                ГДДДДДДДДДДДБДБДДДДДДДДДДДДДДДДДґ
                і       OP15-0                і+10
       УКАЗАТЕЛЬ  ГДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДґ
       ОПЕРАНДА   і OP19-16   і       0       і+12
                ГДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДґ
                і   МАНТИССА 15-0       і+14
                ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
                і   МАНТИССА 31-16      і+16
      ЭЛЕМЕНТ ВЕРШИ-ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
      НЫ СТЕКА:ST   і     МАНТИССА 47-32      і+18
                ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
                і   МАНТИССА 63-48      і+20
                ГДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
                іsі    ПОРЯДОК 14-0           і+22
                ГДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
                і   МАНТИССА 15-0       і+24
                ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
                і   МАНТИССА 31-16      і+26
       СЛЕДУЮЩИЙ    ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
      ЭЛЕМЕНТ СТЕКА і     МАНТИССА 47-32      і+28
                ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
                і   МАНТИССА 63-48      і+30
                ГДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
                іsі    ПОРЯДОК 14-0           і+32
                ГДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
               ...           . . . . . . .        ...
                ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
                і   МАНТИССА 15-0       і+84
                ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
                і   МАНТИССА 31-16      і+86
       ПОСЛЕДНИЙ    ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
       ЭЛЕМЕНТ СТЕКАі     МАНТИССА 47-32      і+88
                ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
                і   МАНТИССА 63-48      і+90
                ГДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
                іsі    ПОРЯДОК 14-0           і+92
                АДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
              ПРИМЕЧАНИЯ:
               S = Знак
               Бит 0 каждого поля - самый правый младший бит соответствующего
               поля регистра
               бит 63 мантиссы - целый бит (подразумевая двоичная точка
               находится рядом с ним, справа)
                  Фиг. 7.15 Состояние 8087

Арифметические команды


      Настоящим "сердцем" микросхемы 8087 является блок выполнения
    арифметических команд.  Сопроцессор 8087 быстро и точно выполняет

            Microsoft (R) Macro Assembler Version 5.00                4/2/89 16:07:21
            Фиг. 7.16 Арифметические команды сопроцессора 8087        Page     1-1
 
                                          PAGE  ,132
                                          TITLE Фиг. 7.16 Арифметические команды сопроцессора 8087
 
             0000                   CODE  SEGMENT
                                          ASSUME      CS:CODE,DS:CODE
 
             0000                   WORD_INTEGER      LABEL WORD
             0000                   SHORT_INTEGER     LABEL DWORD
             0000                   SHORT_REAL  LABEL DWORD
             0000                   LONG_REAL   LABEL QWORD
 
             0000  9B D8 C1               FADD  st(0),ST(1)
             0003  9B D8 C2               FADD  st(0),ST(2)
             0006  9B D8 C2               FADD  ST(0),ST(2)
             0009  9B DC C2               FADD  ST(2),ST(0)
             000C  9B DE 06 0000 R              FIADD WORD_INTEGER
             0011  9B DA 06 0000 R              FIADD SHORT_INTEGER
             0016  9B D8 06 0000 R              FADD  SHORT_REAL
             001B  9B DC 06 0000 R              FADD  LONG_REAL
             0020  9B DE C2               FADDP ST(2),ST(0)
             0023  9B D8 E2               FSUB  st(0),ST(2)
             0026  9B DE 26 0000 R              FISUB WORD_INTEGER
             002B  9B DE EA               FSUBP ST(2),ST(0)
             002E  9B DC E2               FSUBR ST(2),ST(0)
             0031  9B DA 2E 0000 R              FISUBR      SHORT_INTEGER
             0036  9B DE E2               FSUBRP      ST(2),ST(0)
             0039  9B D8 0E 0000 R              FMUL  SHORT_REAL
             003E  9B DE 0E 0000 R              FIMUL WORD_INTEGER
             0043  9B DE CA               FMULP ST(2),ST(0)
             0046  9B D8 F2               FDIV  ST(0),ST(2)
             0049  9B DA 36 0000 R              FIDIV SHORT_INTEGER
             004E  9B DE FA               FDIVP ST(2),ST(0)
             0051  9B D8 FA               FDIVR st(0),ST(2)
             0054  9B DE 3E 0000 R              FIDIVR      WORD_INTEGER
             0059  9B DE F2               FDIVRP      ST(2),ST(0)
 
             005C                   CODE  ENDS
                                          END
 
            Фиг. 7.16 Арифметические команды сопроцессора 8087
    вычислительные операции, и не только четыре основных действия -
    сложение, вычитание, умножение и деление, но также трансцендентные
    и тригонометрические функции.
 
      На Фиг. 7.16 показан ассемблерный листинг программы, состоящей
    из некоторых команд для выполнения основных четырех действий.
    Данный пример иллюстрирует только работу команды FADD, во всех
    возможных комбинациях.  Прежде чем рассматривать команды, разберем
    возможные варианты их работы.  Как видно из Фиг. 7.17, существует
    пять различных методов выполнения арифметических команд.  На
    Фиг. 7.17а показаны пять способов использования данных в
    арифметических командах.  В случае 1 указывается только код
    операции команды.  В операции принимают участие вершина стека и
    элемент ST1, а результат замещает вершину стека.  Заметим, что на
    Фиг. 7.17а приведены примеры для каждого случая с использованием
    команды сложения.  На рисунке также показаны схемы выполнения
    вычислительных операций.
      Случай 2 иллюстрирует операцию, выполняемую с двумя регистрами
    стека сопроцессора 8087.  Одним из этих регистров должна быть
    вершина стека.  Если вершина стека - приемник результата, ее можно
    не упоминать, указав только регистр источника.  Если же приемником
    результата служит какой=то другой регистр, нужно указывать и
    источник, и приемник.
 
      Форма команды           Пример FADD      Действие
     -----------------------------------------------------------------
     1. Fop             FADD        ST0<-ST0+ST1
     2. Fop STi         FADD  ST2   ST0<-ST0+STi
      Fop   ST0,STi     FADD  ST0,ST2 ST0<-ST0+STi
      Fop   STi,ST0     FADD  ST2,ST0 ST2<-ST0+ST2
     3. FopP      STi,ST0     FADDP ST2,ST0 ST2<-ST0+ST2,Извлечь из стека
     4. Fop вещ.пам.    FADD  ВЕЩЕСТ      ST0<-ST0+ВЕЩЕСТ
     5. Flop      целоеПам    FIADD ЦЕЛОЕ ST0<-ST0+ЦЕЛОЕ
                        (a)
 
            Операция          Действие
            ----------------------------------------------------------
            ADD         назначение <- назначение + источник
            SUB         назначение <- назначение - источник
            SUBR        назначение <- источник - назначение
            MUL         назначение <- назначение * источник
            DIV         назначение <- назначение / источник
            DIVR        назначение <- источник / назначение
            ----------------------------------------------------------
                        (b)
 
       Фиг. 7.17 Арифметические операции (a) Комбинации данных
      (b) Арифметические функции для NDP 8087
 
      Случай 3 представляет собой стековую операцию.  Команда такого
    формата выполняет операцию с двумя стековыми операндами, а затем
    уничтожает вершину стека.  Так как данные из вершины стека
    извлекаются из него после операции, она не может быть приемником
    результата этой операции.  Если приемник результата - элемент ST1,
    то операция становится классической стековой.  Классическая
    стековая операция удаляет из вершины стека два верхних элемента,
    использует их в требуемой операции, а затем помещает результат
    назад в стек.  Конечно в качестве приемника результата этой
    операции может быть указан любой из регистров.
 
      Два оставшихся варианта используют операнды в памяти.  В случае
    4 операнд в памяти - это короткое или длинное действительное число.
    В случае 5 операнд - это короткое целое или целое слово.
 
      Вернувшись к Фиг. 7.16 можно заметить, что в команде FADD
    определены только четыре операнда в памяти.  Это два целых числа -
    короткое и слово, и два действительных числа - короткое и длинное.
    Арифметические команды не могут непосредственно работать с
    десятичным, длинным целым и временным действительным форматами
    чисел, и перед использованием их в счете программа должна загрузить
    такие числа в регистр.
 
      На Фиг. 7.17 показаны шесть существующих арифметических команд.
    В процессоре 8087 стандартные четыре арифметические операции
    дополнены обратными операциями деления и вычитания.  Поскольку
    сложение и умножение коммутативны, обратных операций для них
    вводить не надо; порядок же операндов у вычитания и деления
    критичен.  И иногда бывает, что число, находящееся в регистре
    источника - не то число, которое нужно вычитать из
    регистра=приемника; в этом случае вычитание правильно выполнит
    обратная операция.

Команды сравнения


      Как и в наборе команд микропроцессора 8088, у сопроцессора 8087
    есть команды, сравнивающие два числа.  Сопроцессор 8087 отбрасывает
    результат сравнения, но устанавливает в соответствии с ним флаги
    состояния.    Перед тем как опросить флаги состояния, программа
    должна считать слово состояния в память.  Далее проще всего
    загрузить флаги состояния в регистр AH, а затем, для легкости
    проверки условия, - в регистр флагов макропроцессора 8088.
 
      На Фиг. 7.18 показан листинг ассемблера команд сравнения
    сопроцессора 8087.  Так как в операции всегда участвует вершина
    стека, в программе надо указать только один регистр или операнд в
    памяти.  После сравнения в слове состояния содержится информация о
    соотношении двух чисел.  Таблица на Фиг. 7.19 показывает возможные
    варианты соотношений.  Для отражения результата сравнения
    необходимы только два бита состояния - C3 и C0; расположение C3 и
    C0 в слове состояния показано на Фиг. 7.8.
      На Фиг. 7.20 приведен фрагмент программы, которая сравнивает
    содержимое вершины стека и слово в памяти.  Переходы в этой
    программе выполняются на основе результата сравнения.  В одном из
    четырех случаев числа нельзя сравнить.  Это происходит тогда, когда
    одно из чисел есть NAN (не число) или одна из форм бесконечности.
            Microsoft (R) Macro Assembler Version 5.00                1/1/80 04:04:18
            Фиг. 7.18 Команды сравнения сопроцессора 8087             Page     1-1
 
                                          PAGE  ,132
                                          TITLE Фиг. 7.18 Команды сравнения сопроцессора 8087
 
             0000                   CODE  SEGMENT
                                          ASSUME      CS:CODE,DS:CODE
 
             0000                   WORD_INTEGER      LABEL WORD
             0000                   SHORT_INTEGER     LABEL DWORD
             0000                   SHORT_REAL  LABEL DWORD
             0000                   LONG_REAL   LABEL QWORD
 
             0000  9B D8 D1               FCOM
             0003  9B D8 D2               FCOM  ST(2)
             0006  9B DE 16 0000 R              FICOM WORD_INTEGER
             000B  9B D8 16 0000 R              FCOM  SHORT_REAL
             0010  9B D8 D9               FCOMP
             0013  9B DA 1E 0000 R              FICOMP      SHORT_INTEGER
             0018  9B DC 1E 0000 R              FCOMP LONG_REAL
             001D  9B DE D9               FCOMPP
             0020  9B D9 E4               FTST
             0023  9B D9 E5               FXAM
 
 
             0026                   CODE  ENDS
                                          END
 
            Фиг. 7.18 Команды сравнения сопроцессора 8087
 
    Сопроцессор 8087 помещает флаги состояния C3 и C0 в точности на те
    же места, которые в регистре флагов микропроцессора 8088 занимают
    флаги нуля C3 и переноса C0.  Как показано на рисунке, если
    программа переслала старший байт слова состояния сопроцессора 8087
    в регистр флагов микропроцессора 8088, она может выполнить условный
    переход с помощью непосредственной проверки состояния флагов с
    использованием команды условного перехода, и не нужно выделять и
    проверять отдельные биты слова состояния.
 
      С3    С0          Порядок
     --------------------------------------------------------------
      0     0     ST > источник
      0     1     ST < источник
      1     0     ST = источник
      1     1     ST и источник несравнимы
     --------------------------------------------------------------
 
      Фиг. 7.19 Порядок сравнения
 
      На Фиг. 7.18 демонстрируется, что существуют и другие варианты
    команды FCOM, в том числе и то, что для команды FCOM существует
    версия FICOM необходимая для ставнения целых чисел.  Команды FCOMP
    и FICOMP идентичны командам FCOM и FICOM, за исключением того, что
    сопроцессор 8087 извлекает из стека содержимое вершины после
    выполнения операции.  Это позволяет сравнить два числа с помощью
    сопроцессора 8087, не беспокоясь о том, что нужно удалить из стека
    первый операнд после операции.
 

            Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:04:23
            Фиг. 7.20 Условный переход                        Page         1-1
 
 
                                          PAGE    ,132
                                          TITLE   Фиг. 7.20 Условный переход
 
             0000                   CODE    SEGMENT
                                          ASSUME  CS:CODE,DS:CODE
 
             0000                   WORD_INTEGER    LABEL   WORD
             0000  ????             STATUS_WORD     DW      ?
 
                                    ;-----  Сравнивается WORD_PTR со словом на вершине стека сопроцессора 8087
 
             0002  9B DE 16 0000 R              FICOM   WORD_INTEGER    ; Сравнение слова с ST
             0007  9B DD 3E 0000 R              FSTSW   STATUS_WORD     ; Сохранение слова состояния 8087
             000C  9B                     FWAIT             ; Синхронизация процессора с сопроцессором
             000D  8A 26 0001 R                 MOV     AH,BYTE PTR STATUS_WORD+1
             0011  9E                     SAHF              ; Занесение флагов (C0=CF,C3=ZF)
             0012  72 02                        JB      CONTINUE        ; Проверка флага C0,переход если 0
             0014  75 00                        JNE     ST_GREATER      ; Проверка флага C3
             0016                   ST_EQUAL:               ; Попадаем сюда,если C3=1,C0=0 -
                                    ; ...                   ;  значения равны
             0016                   ST_GREATER:             ; Попадаем сюда,если C3=0,C0=0 - число
                                    ; ...                   ;  на вершине стека 8087 больше WORD_PTR
             0016                   CONTINUE:
             0016  75 00                        JNE     ST_LESS         ; Проверка флага C3
             0018                   UNORDERED:              ; Попадаем сюда,если C3=1,C0=1 - невоз-
                                    ; ...                   ;  можно определить какое из чисел больше
             0018                   ST_LESS:                      ; Попадаем сюда,если C3=0,C0=1 - число
                                    ; ...                   ;  на вершине стека 8087 меньше WORD_PTR
 
             0018                   CODE    ENDS
                                          END
 
            Фиг. 7.20 Условный переход
 
      У команды FCOMPP программист не указывает никаких операндов.
    Эта команда всегда сравнивает верхние два элемента стека.  После
    сравнения они оба исчезают из стека.
 
      Команды сравнения с извлечением из стека обеспечивают удобный
    способ очистки стека.  Поскольку у сопроцессора 8087 нет команды,
    удобно извлекающей операнд из стека, вместо нее можно использовать
    команды сравнения с извлечением из стека.  Эти команды также
    изменяют и регистр состояния, и их нельзя использовать, если биты
    состояния имеют значение для дальнейшей работы, но в большинстве
    случаев эти команды позволяют быстро извлечь из стека один или два
    операнда.  Так как сопроцессор 8087 регистрирует ошибку при
    переполнении стека, необходимо удалить все операнды из стека при
    окончании вычислений.
 
      Существуют две специальные команды сравнения.  Команда
    сравнения содержимого вершины стека с нулем FTST, с помощью которой
    можно быстро определить знак содержимого вершины стека.  (Результат
    сравнения иллюстрируется таблицей на Фиг. 7.19, нужно только всюду в
    таблице слово "источник" заменить на слово "нуль").
 
      Команда FXAM, строго говоря, не является командой сравнения.
    Хотя она и работает с содержимым вершины стека, но не сравнивает
    содержимое вершины ни с одним другим числом.  Скорее команда FXAM
    устанавливает все четыре флага регистра состояния (от C3 до C0
    включительно), показывая, какого типа число находится в вершине
    стека.  Так как сопроцессор 8087 может обрабатывать любые формы
    чисел, а не только нормализованные числа с плавающей точкой,
    команда FXAM определит, что же находится в вершине стека.  На
    Фиг. 7.21 показаны значения битов состояния в каждом случае.
 
      Если при арифметической обработке вы не делаете что=либо из
    ряда вон выходящее и не работаете на пределе разрядной сетки
    сопроцессора 8087, то не нужно рассматривать никакие из приведенных
    выше результатов команды FXAM; вы должны ожидать увидеть лишь
    положительные либо отрицательные нормализованные числа или нули.
    Если выясняется, что вершина стека пустая, то обычно выдается
    ошибка.  Программа может сделать такую проверку для контроля
    параметра, переданного в вершине стека.
 
      Остальные значения - это реакция микросхемы 8087 на ошибку.
    Когда сопроцессор 8087 обнаруживает ошибку, он пытается возбудить
    прерывание по особой ситуации, устанавливая соответствующие биты в
    слове состояния.  Однако если ситуация замаскирована с помощью
    управляющего слова, сопроцессор 8087 сам реагирует на нее.    Он
    решает, какая реакция соответствует данной ошибке и возбуждает
    появление специфического числа в регистре.  Например, результат NAN
    возникает, если операция некорректна, как в случае извлечения
    квадратного корня из отрицательного числа.  Бесконечность
    появляется, если результат операции слишком велик для представления
    с плавающей точкой.

      C3    C2    C1    C0    Значение          С3    С2    С1    С0    Значение
      ---------------------------------------------------------------------------------------------------
       0    0    0     0     + Ненормально           1    0     0     0     + 0
       0    0    0     1     + NAN             1    0     0     1     Пусто
       0    0    1     0     - Ненормально           1    0     1     0     - 0
       0    0    1     1     - NAN             1    0     1     1     Пусто
       0    1    0     0     + Нормально       1    1     0     0     + Ненормальное
       0    1    0     1     + Бесконечность    1    1     0     1     Пусто
       0    1    1     0     - Нормально       1    1     1     0     - Ненормальное
       0    1    1     1     - Бесконечность    1    1     1     1     Пусто
      ---------------------------------------------------------------------------------------------------

                Фиг. 7.21 Пример интерпретации кодов состояний
Денормализованные и ненормализованные числа возникают при
    достижении другого предела представления чисел с плавающей точкой.
    Когда число становится настолько маленьким, что у него уже нет
    смещенного порядка, оно становится денормализованным.  Вместо того,
    чтобы заменить число нулем, процессор 8087 записывает наименьшее
    значение порядка и ненормализованную мантиссу.  Это означает, что
    число теряет точность, так как у его мантиссы появляются незначащие
    нули.  Но денормализованный результат все же точнее, чем просто
    нуль.  И это тот случай, при котором сопроцессор 8087, продолжая
    работу, наилучшим образом реагирует на плохие условия.  Пометка
    числа, как денормализованного, должна насторожить вас.  При этом
    также устанавливается бит денормализации в регистре особых
    ситуаций, и остается установленным до тех пор, пока команда FCLEX
    не сбросит биты особых ситуаций.  С помощью анализа флагов
    программа может зарегистрировать возникновение ошибки и отнестись к
    результатам с подозрением.

Степенные и тригонометрические функции


      Оставшаяся группа команд сопроцессора 8087 выполняет вычисления
    сложных математических функций.  Эти команды позволяют микросхеме
    8087 вычислять арифметические выражения, требующие логарифмов,
    экспонент и тригонометрических функций.  На Фиг. 7.22 изображен
    список этих команд.  В этом списке нет тех операций, которые есть у
    карманного калькулятора.  Конструкторы процессора 8087 не смогли
    реализовать все нужные функции на одном кристале, так как это
    оказалось слишком сложным.      Вместо этого они создали набор функций
    более низкого уровня, из которых можно программно построить функции
    калькулятора.  Например, таких тригонометрических функций как синус

           Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:04:28
           Фиг. 7.22 Арифметические команды над вершиной стека 8087     Page   1-1
 
                                         PAGE    ,132
                                         TITLE   Фиг. 7.22 Арифметические команды над вершиной стека 8087
 
            0000                   CODE    SEGMENT
                                         ASSUME  CS:CODE,DS:CODE
 
            0000  9B D9 FA                     FSQRT
            0003  9B D9 FD                     FSCALE
            0006  9B D9 F8                     FPREM
            0009  9B D9 FC                     FRNDINT
            000C  9B D9 F4                     FXTRACT
            000F  9B D9 E1                     FABS
            0012  9B D9 E0                     FCHS
            0015  9B D9 F2                     FPTAN
            0018  9B D9 F3                     FPATAN
            001B  9B D9 F0                     F2XM1
            001E  9B D9 F1                     FYL2X
            0021  9B D9 F9                     FYL2XP1
 
            0024                   CODE    ENDS
                                         END
 
          Фиг. 7.22 Арифметические команды над вершиной стека 8087
    и косинус нет; вместо этих функций есть функция частичного
    тангенса.  Эта функция вычисляет отношение, равное тангенсу угла.
    Из этого отношения программа может определить синус, косинус,
    тангенс и любую другую тригонометрическую функцию.      Из обратных
    тригонометрических функций есть операция частичного арктангенса,
    которая берет значения отношения чисел и вычисляет угол этого
    тангенса.  Эта частичная функция позволяет сконструировать
    арксинус, арккосинус и другие функции, не имея их в явном виде.
      Ниже приведен список команд этой группы с коротким комментарием
    работы каждой команды.  Ни у одной из этих команд не пишутся
    операнды, так как все они работают с вершиной стека, и возможно,
    также с элементом ST1.
 
      FSQRT   (квадратный  корень)
      ST    квадратный корень из (ST)
      ST      должно быть неотрицательно.
      FSCALE  (масштабирование) ST <- ST * 2ST1
 
      Эта команда необходима для возведения в степень.  Другая, и
    притом единственная, функция возведения в степень имеет ограничения
    на значение показателя.  Эта команда возводит 2 в степень, равную
    целому числу.  Далее будет приведен пример возведения 10 в
    произвольную степень.
      FPREM (частичный остаток)
      ST <- ST mod ST1  (частичный)
 
      Полностью операцию деления по заданному модулю команда FPREM не
    выполняет.    Эта команда за один раз уменьшает содержимое вершины
    стека максимум на 264.  Команда выполняет нахождение истинного
    остатка и требует очень много времени для уменьшения большого числа
    по очень маленькому основанию.  Максимально уменьшая число в
    течение каждого выполнения команды, программист может разрешить
    прерывания в течение всей операции поиска остатка.      Если функция не
    завершена, команда FPREM устанавливает флаг C2 равным 1, а когда
    завершает вычисление остатка, то устанавливает другие три флага C3,
    C1 и C0 равными трем младшим битам частного.  Когда команда FPREM
    используется в тригонометрических примитивах для ограничения
    величины угла, это оказывается необходимым для определения октанта
    первоначального угла.  Чтобы продемонстрировать работу этой
    команды, мы далее приведем пример тригонометрических вычислений.
 
      FRNDIN         T  (округление до целого)
          ST <- Integer(ST)
 
      Эта команда округляет текущее содержимое вершины стека до
    целого числа.  Текущее управляющее слово определяет направление
    округления.
 
      FXTRACT    (извлечение)
           ST <- дробная часть ST
           ST1   показатель степени двойки ST
      Эта команда разбивает текущую вершину стека на компоненты.
    Аргументом этой функции является вершина стека.  Значение
    показателя степени замещает содержимое вершины стека, а затем
    дробная часть аргумента помещается в стек и становится новой
    вершиной стека.  Действия этой команды FXTRACT обратны действиям
    команды FSCALE.  Если в вершине стека находится некоторое число, то
    выполнение последовательно команд FXTRACT и FSCALE оставляет в
    вершине стека то же число.      Но команда FSCALE не удаляет из стека
    показатель степени, так что теперь в стеке дополнительно окажется
    еще одно число.
 
      FABS   (абсолютная величина)
        ST <- абсолютное значение ST
 
      Эта команда устанавливает у числа в вершине стека знаковый
    разряю в нуль (что соответствует положительному значению).
 
      FCHS   (смена знака)
        ST <- -ST
 
      Эта команда изменяет знак у вершины стека.
 
      Следующие команды выполняют также трансцендентные функции, как
    тригонометрические, а также логарифмические и возведение в степень.
 
      FPTAN  (частичный тангенс)
        ST <- X
        ST1 <- Y, где Y/X = TAN(угол)
 
      Эта команда позволяет вычислить все тригонометрические функции.
    Исходное число - угол, выраженный в радианах, значение которого
    должно быть в интервале 0 < угол < PI/4, - помещается в вершину
    стека.  Уменьшить угол до правильного значения можно с помощью
    команды FPREM.  Результатом является отношение Y/X, которое равно
    тангенсу угла; Y замещает вершину стека, а затем в стек помещается
    X.      Другие тригонометрические функции можно вычислить с
    использованием этих значений; например, косинус COS(угол) =
    X/SQRT(X2 + Y2).
 
      FPATAN      (частичный арктангенс)
         ST <- Arctan(Y/X) = Arctan(ST1/ST)
 
      Эта функция дополняет предыдущую, FPTAN.  Команда FPATAN
    вычисляет угол в соответствии с отношением чисел ST1 и ST0.  Она
    извлекает из стека число X, а затем записывает результирующий угол
    вместо числа Y в качестве новой вершины стека.  Исходные значения
    должны подчиняться неравенству
 
      0 < Y < X < бесконечность
      F2XM1  (два в степени X минус 1)
         ST <- 2ST - 1
 
      Эта функция выполняет возведение в степень; она возводит 2 в
    степень, указанную в вершине стека.  Исходное число должно
    находиться в диапазоне 0 <= ST <=0.5, и чтобы вычислить два в
    степени, большей 0.5, эту команду нужно использовать вместе с
    командой FSCALF.  С помощью команд FLD, загружающих специальные
    константы, программа может возвести в заданную степень также числа,
    отличные от 2, для этого используются следующие формулы:
 
      10**X = 2**(X*Log2(10))
      e**X = 2**(X*Log2(e))
      Y**X = 2**(X*Log2(Y))
 
      Далее приводится пример возведения 10 в произвольную степень.
 
      FYL2X (Y умножить на логарифм по основанию 2 от X)
         ST <- Y*Log2(X) = ST1*Log2(ST)
 
      Эта функция выполняет операцию логарифмирования.  Она берет
    логарифм по основанию 2 от содержимого вершины стека и затем
    умножает его на элемент ST1.  Команда FYL2X извлекает из стека
    число X и замещает результат числом Y.  Исходные числа должны
    удовлетворять следующим соотношениям:
 
      0 < X < бесконечности и - бесконечность < Y < бесконечности.
    Эта функция позволяет вычислять логарифмы и по основаниям, отличным
    от 2.  Следующая формула:
 
      Logn(2) * Log2(X)
 
      вычисляет логарифм числа X по основанию n; значение Logn2
    вычисляется как 1/(Log2n).
 
      FYL2XP1  (Y умножить на логарифм по основанию 2 от X+1)
          ST <- Y*Log2(X+1) = ST1*Log2(ST+1)
 
      Эта функция идентична функции FYL2X за исключением того, что к
    X прибавляется 1.  Функция FYL2XP1 накладывает более жесткие
    ограничения на X, и предназначена для вычисления логарифмов чисел,
    значения которых очень близки к 1.    Эта функция дает наиболее
    высокую точность, если
 
      0 < ABS(X) < 1 - (корень из 2 )/2

Степени десяти


      Первый пример - исходный текст программы на Фиг. 7.23.      Эта
    программа распечатывает короткое действительное представление
    степеней 10 от 103 до 1039.  Как мы уже видели в разделе,
    посвященном представлению данных, Макроассемблер фирмы IBM не имеет
    возможностей непосредственно генерировать действительные числа.
    Наличие такой таблицы чисел облегчит вам представление в виде
    констант степеней 10.  По этой таблице вы сможете определить
    шестнадцатеричное представление числа, которое нужно использовать в
    программе.
 
      Программа вычисляет только каждую третью степень 10, и
    использует короткий действительный формат.  Если вы работаете с
    много большими числами, или вам нужна большая точность, тогда нужно
    выполнить этот пример, используя длинный действительный формат
    чисел, а также построить каждую степень 10.  Выполните это в
    качестве упражнения.
 
      Первоочередная цель этого примера - введение в программирование
    и работу на сопроцессоре 8087.  Это отдельная самостоятельная
    программа, предназначенная для выполнения в виде файла типа .EXE.
    Прежде чем разобраться в самой программе, заметим, что в нее входит
    сегмент STACK, необходимый в файле типа .EXE.  Сначала в сегменте
    CODE записаны поля данных, а выполнение программы начинается с
    метки CALCULATE_POWER.  Заглянув вперед, на оператор END, вы
    увидите, что первой выполняемой командой будет команда, помеченная
    CALCULATE_POWER, так как она указана в операторе END.
 
      Первая часть программы выполняет инициализацию.  Перед
    загрузкой указателя на сегмент CODE в регистр DS программа помещает
    в стек адрес возврата из файла типа .EXE.  Затем с помощью команды
    FINIT инициализируется сопроцессор 8087, что аналогично аппаратному
    сбросу.  Тем самым сопроцессор 8087 оказывается настроенным на
    обработку особых ситуаций по умолчанию, что наилучшим образом
    подходит для примеров этой книги.  Команда FINIT также сбрасывает
    регистровый стек сопроцессора 8087, освобождая все его восемь
    позиций.  Программа должна использовать команду FINIT только в
    момент запуска.  Команда FINIT никогда не должна быть использована
    внутри подпрограммы для сопроцессора 8087.
 
      Следующие команды загружают число 1000 в регистр ST1 и число 1
    в регистр ST0.  Все следующие команды сопроцессора 8087 используют
    эти два регистра стека.  В регистре ST0 находится текущая степень
    десяти, а в регистре ST1 находится значение 103.  Мы будем
    использовать число в регистре ST1 для увеличения числа в регистре
    ST0 после каждой итерации программы.  Целая переменная POWER
    содержит текущую степень 10, находящуюся в регистре ST0.
 
      После метки POWER_LOOP элемент ST0 умножается на элемент ST1,
    (в котором содержится число 1000), чтобы увеличить содержимое
    регистра ST0 в 103 раз.  Команда FST записывает результат в память.
    Оставшаяся часть программы после метки POWER_LOOP печатает
    результаты вычислений.  В подпрограмме TRANSLATE шестнадцатеричный
    байт преобразуется в двухбайтовую строку в коде ASCII так, что
    программа может его распечатать.  Текущее значение POWER (степень
    десяти), а также шестнадцатеричная строка, записанная процессором
    8087, преобразуются в код ASCII.  Затем функция DOS печатает строку
    на дисплее.  Цикл POWER_LOOP продолжается до тех пор, пока
    последнее напечатанное значение не станет больше 1038.  Это
    значение выбрано потому, что 1038 - это максимальное число, которое
    может быть представлено в коротком действительном формате.    Если бы
    использовался длинный действительный формат чисел, это значение
    было бы равно 10308.  Заключительная часть Фиг. 7.23 показывает,
    как выглядит результат работы этой программы на дисплее.
 

           Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:04:33
           Фиг. 7.23 Степени 10                             Page   1-1
 
 
                                         PAGE    ,132
                                         TITLE   Фиг. 7.23 Степени 10
 
            0000                   STACK   SEGMENT STACK
            0000  0040[                  DW      64 DUP (?)
                  ????
                              ]
 
            0080                   STACK   ENDS
 
            0000                   CODE    SEGMENT
                                         ASSUME  CS:CODE
 
            0000  ????????               POWER_OF_TEN    DD      ?                 ; Область данных для 10**x,
                                                                 ;      короткое плавающее
            0004  0002[            OUTPUT_POWER    DB      2 DUP (' ')     ; Текстовый буфер для значения
                  20
                              ]
 
            0006  48 20 20 20 20                     DB      'H    '         ;  степени
            000B  0008[            OUTPUT_STRING   DB      8 DUP ('         ')     ; Текстовый буфер для результата -
                  20 20 20 20 20
                  20 20 20 20
                              ]
            0053  48 0D 0A 24                        DB      'H',13,10,'$'   ; Конец строки
            0057  00                     POWER       DB      0               ; Текущая степень 10
            0058  03E8             THOUSAND          DW      1000      ; Константа
            005A  03BF             CONTROL_87      DW      03BFH
 
            005C                   CALCULATE_POWER PROC    FAR
            005C  1E                           PUSH    DS        ; Занесение в стек адреса возврата
            005D  B8 0000                      MOV     AX, 0
            0060  50                           PUSH    AX
            0061  0E                           PUSH    CS
            0062  1F                           POP     DS
                                         ASSUME  DS:CODE         ; Адресация области данных
            0063  9B DB E3                     FINIT             ; Инициализация сопроцессора 8087
 
                  Фиг. 7.23 (а) (начало)
            0066  9B DF 06 0058 R              FILD    THOUSAND        ; Загрузка 10**3 в стек сопроцессора 8087
            006B  9B D9 E8                     FLD1              ; Загрузка начального значения в стек 8087
            006E                   POWER_LOOP:
            006E  9B DC 8E 0000                FMUL    ST(1)           ; Умножение ST(0) на ST(1)
            0073  9B D9 16 0000 R              FST     POWER_OF_TEN    ; Сохранение в памяти результата
            0078  80 06 0057 R 03              ADD     POWER, 3        ; Увеличение показателя степени
            007D  A0 0057 R                    MOV     AL, POWER       ; Выборка показателя степени
            0080  8D 1E 0004 R                 LEA     BX, OUTPUT_POWER
            0084  E8 00AC R                    CALL    TRANSLATE
            0087  B9 0004                      MOV     CX, 4
            008A  8D 1E 000B R                 LEA     BX, OUTPUT_STRING
            008E  8D 36 0003 R                 LEA     SI, POWER_OF_TEN+3
            0092  FD                           STD               ; Установка пересылки с уменьшением адреса
            0093                   VALUE_OUTPUT:
 
            0093  AC                           LODSB             ; Выборка байта результата
            0094  E8 00AC R                    CALL    TRANSLATE       ; Занесение символа в выводимую строку
            0097  E2 FA                  LOOP    VALUE_OUTPUT    ; Цикл по всем байтам результата
 
            0099  8D 16 0004 R                 LEA     DX, OUTPUT_POWER
            009D  B4 09                  MOV     AH, 9H
            009F  CD 21                  INT     21H
            00A1  80 3E 0057 R 26              CMP     POWER, 38
            00A6  72 C6                  JB      POWER_LOOP
            00A8  9B DE D9                     FCOMPP            ; Удаление из стека двух чисел
            00AB  CB                           RET
            00AC                   CALCULATE_POWER ENDP
 
            00AC                   TRANSLATE         PROC    NEAR
            00AC  50                           PUSH    AX        ; Сохранение исходного значения
            00AD  51                           PUSH    CX
            00AE  B1 04                  MOV     CL, 4           ; Сдвиг старшей цифры выводимого числа
            00B0  D2 E8                  SHR     AL, CL          ;      на место младшей цифры
            00B2  59                           POP     CX
            00B3  E8 00CB R                    CALL    XLAT_OUTPUT     ; Вывод старшей цифры выводимого числа
            00B6  58                           POP     AX        ; Восстановление младшей цифры
            00B7  E8 00CB R                    CALL    XLAT_OUTPUT     ; Вывод младшей цифры выводимого числа
            00BA  C3                           RET
            00BB                   TRANSLATE         ENDP
 
            00BB  30 31 32 33 34 35 36     ASCII_TABLE     DB      '0123456789ABCDEF'
                37 38 39 41 42 43 44
                45 46
            00CB                   XLAT_OUTPUT     PROC    NEAR
            00CB  24 0F                  AND     AL, 0FH         ; Выделение младшей цифры
            00CD  53                           PUSH    BX
            00CE  8D 1E 00BB R                 LEA     BX, ASCII_TABLE ; Адрес таблицы трансляции
            00D2  D7                           XLAT    ASCII_TABLE     ; Преобразование в символьную форму
            00D3  5B                           POP     BX
            00D4  88 07                  MOV     [BX], AL        ; Сохранение очередного символа результата
            00D6  43                           INC     BX        ; Переключение на следующий символ
            00D7  C3                           RET
            00D8                   XLAT_OUTPUT     ENDP
            00D8                   CODE    ENDS
                                         END     CALCULATE_POWER
                  Фиг. 7.23 (а) (продолжение)
            A>PRINT10
            03H   447A0000H
            06H   49742400H
            09H   4E6E6B28H
            0CH   5368D4A5H
            0FH   58635FA9H
            12H   5D5E0B6BH
            15H   6258D727H
            18H   6753C21CH
            1BH   6C4ECB8FH
            1EH   7149F2CAH
            21H   76453719H
            24H   7B4097CEH
            27H   7F800000H
 
                  Фиг. 7.23 (b)
 
          Фиг. 7.23 (a) Степени 10; (b) Вывод процедуры степеней 10
 
      Вам нужно посмотреть часть программы TRANSLATE, несмотря на то,
    что она не использует ни одной команды сопроцессора 8087.  Эта
    часть - пример подготовки чисел к печати.  В частности, команда
    XLAT преобразует для печати шестнадцатеричную тетраду (значение от
    0 до 0FH) в правильный символ кода ASCII (от 0 до F).  Просто
    прибавлять значение тетрады к значению 0 нельзя, так как в коде
    ASCII символы от A до F не следуют непосредственно за символами от
    0 до 9; преобразование прекрасно выполняет команда перекодировки.
    Мы используем аналогичный метод, когда преобразуем число с
    плавающей точкой в пригодную для печати десятичную форму.

Десять в степени X


      Второй пример использования сопроцессора 8087 гораздо глубже
    раскрывает перед нами его работу.  Этот пример - подпрограмма,
    которая будет использоваться в дальнейшем.  Подпрограмма
    предполагает, что исходное число находится в вершине стека; после
    возврата из подпрограммы в вершине стека находится число, равное
    десяти в степени X.  Исходный текст этой подпрограммы приведен на
    Фиг. 7.24.
      Сопроцессор 8087 не имеет команды возведения 10 в произвольную
    степень, но мы можем возводить в любую степень двойки.  Поэтому
    нужжно пользоваться формулой
 
      10**X = 2**(X*Log2(10))
 
      Первые две команды программы формируют показатель степени двух.
    Программа загружает константу Log210, а затем умножает ее на
    исходное число X, давая необходимую степень 2, называемую здесь E.
    Поле комментариев в примере используется для иллюстрации элементов
    стека сопроцессора 8087.  Символ "?" означает, что значение
            Microsoft (R) Macro Assembler Version 5.00             1/1/80 04:04:40
            Фиг. 7.24 Вычисление 10**ST                       Page         1-1
 
                                          PAGE    ,132
                                          TITLE   Фиг. 7.24 Вычисление 10**ST
             0000                   CODE    SEGMENT PUBLIC
                                          ASSUME  CS:CODE,DS:CODE
                                          PUBLIC  TEN_TO_X
             0000  ????             OLD_CW  DW      ?
             0002  ????             NEW_CW  DW      ?
                                    ;--------------------------------------------
                                    ; Эта программа извлекает число с вершины стека
                                    ;  сопроцессора 8087 и возводит 10 в эту степень.
                                    ; Параметры: X в ST(0)
                                    ; Результат: 10**X в ST(0)
                                    ; Эта программа использует две ячейки в стеке 8087
                                    ;  плюс параметр - всего три ячейки.
                                    ;--------------------------------------------
             0004                   TEN_TO_X          PROC    NEAR
                                                            ;----ST(0)------;-----ST(1)-----;--ST(2)--
                                                            ; X         ; ?         ; ?
             0004  9B D9 E9                     FLDL2T                  ; LOG2(10)      ; X            ; ?
             0007  9B DE C9                     FMULP   ST(1),ST(0)     ; X*LOG2(10)=E  ; ?            ; ?
             000A  D9 3E 0000 R                 FNSTCW  OLD_CW          ;---------------;---------------;--------
             000E  9B                     FWAIT             ; Выборка текущего слова состояния
             000F  A1 0000 R                    MOV     AX,OLD_CW      ; Сохранение слова сотояния
             0012  25 F3FF                      AND     AX,NOT 0C00H   ; Установка способа округления к минус
             0015  0D 0400                      OR      AX,0400H       ;      бесконечности
             0018  A3 0002 R                    MOV     NEW_CW,AX
             001B  9B D9 2E 0002 R              FLDCW   NEW_CW          ;---------------;---------------;--------
             0020  9B D9 E8                     FLD1              ; 1         ; E         ; ?
             0023  9B D9 E0                     FCHS              ; -1        ; E         ; ?
             0026  9B D9 C1                     FLD     ST(1)           ; E         ; -1        ; E
             0029  9B D9 FC                     FRNDINT                 ; INT(E) = I    ; -1            ; E
             002C  9B D9 2E 0000 R              FLDCW   OLD_CW          ;           ;           ;
             0031  9B D9 CA                     FXCH    ST(2)           ; E         ; -1        ; I
             0034  9B D8 E2                     FSUB    ST(0),ST(2)     ; E - I = F     ; -1            ; I
             0037  9B D9 FD                     FSCALE                  ; F*2**-1 = F/2 ; -1            ; I
             003A  9B D9 F0                     F2XM1             ; (2**F/2)-1    ; -1          ; I
             003D  9B DE E1                     FSUBRP  ST(1),ST(0)     ; 2**F/2          ; I            ; ?
             0040  9B D8 8E 0000 U              FMUL    ST(0)           ; 2**F            ; I            ; ?
             0045  9B D9 FD                     FSCALE                  ; (2**F)*(2**I) ; I            ; ?
             0048  9B D9 C9                     FXCH    ST(1)           ; I         ; 2**(I+F)      ; ?
             004B  9B D8 D9                     FCOMP             ; 2**(I+F)      ; ?           ; ?
             004E  C3                     RET               ; 10**X           ; ?         ; ?
             004F                   TEN_TO_X          ENDP
             004F                   CODE    ENDS
                                          END
 
                  Фиг. 7.24 Вычисление 10**ST
 
    соответствующего элемента стека неопределено.  В подпрограмме
    используется всего три элемента стека, так что в поле комментариев
    есть три колонки.
      Хотя теперь мы имеем нужную степень двух, у сопроцессора 8087
    отсутствует команда, завершившая бы всю работу за один шаг.
    Команда F2XM1 возводит 2 в степень X, но только если X меньше или
    равен 1/2.    Это означает, что степень E мы должны разделить на
    целую и дробную части; затем команда FSCALE сможет возвести 2 в
    целую степень, а команда F2XM1 обработает дробную часть.
 
      Перед разделением E на две части программа выполняет некоторые
    вспомогательные действия.  Эти действия - команды сопроцессора
    8087, которые читают управляющее слово и устанавливают режим
    округления в направлении меньшего числа.  Теперь, когда мы возьмем
    целую часть показателя степени, его значение будет округляться
    влево, в направлении минус бесконечности.  Дробная часть показателя
    будет положительным числом, что также требуется для команды F2XM1.
 
      Обратите внимание на использование команды FWAIT после команды
    FNSTCW.  Ожидать окончания выполнения умножения перед записью
    управляющего слова не надо, так как умножение не меняет код в
    управляющем слове.  Но перед чтением управляющего слова из главной
    памяти и модификацией его нужна гарантия, что сопроцессор 8087 уже
    завершил запись.  Значит, нужно выполнить команду ожидания FWAIT
    перед чтением.
 
      После установки способа округления команда FRNDINT округляет
    показатель степени E до целого значения.  Так как мы также
    запомнили и исходное значение E в стеке, можно вычесть целую часть
    из E и получить дробную часть показателя степени.  То есть теперь E
    = I + F, и можно записать
 
      2**E = 2**I*2**F
 
      Но перед тем обратим внимание на одну маленькую деталь.
    Дробная часть F может оказаться значением большим 1/2, и поэтому не
    может быть аргументом команды F2XM1.  Сейчас мы используем число
    -1, ранее помещенное в стек, чтобы разделить F на 2, получив при
    этом F/2.  Чтобы это сделать, воспользуемся командой FSCALE, так
    как эта команда умножает содержимое ST0 на 2 в степени,
    содержащейся в ST1.  Поскольку в элементе ST1 содержится -1,
    результирующим эффектом будет умножение на 1/2.  Теперь можно
    утверждать, что содержимое регистра ST0 меньше 1/2.
 
      Далее команда F2XM1 возводит 2 в степень F/2, а -1 в стеке
    помогает ликвидировать -1, порождаемую в результате работы команды
    F2XM1.  Обратное вычитание с извлечением из стека избавляется и от
    -1 в стеке.  Затем 2F/2 умножается само на себя, в элементе ST0
    получается число 2F.  Так как целая часть показателя степени теперь
    переместилась в элементе ST1, команда FSCALE умножает 2I на число
    2F, которое уже находится в элементе ST0, давая искомый результат.
    Команда FCOMP удаляет из стека число I перед возвратом из
    подпрограммы.

Изображение чисел с плавающей точкой


      Следующая подпрограмма берет число из вершины стека и
    преобразует его в печатную строку символов.  Фактически,
    подпрограмма извлекает число с вершины стека и посылает его на
    дисплей.  Далее эта подпрограмма будет использована в двух примерах
    для вывода их результатов.      Исходный текст программы показан на
    Фиг. 7.25.
 
      Эта подпрограмма достаточно просто строит выводимую символьную
    строку.  Если исходное число NAN, либо бесконечность, или другое
    специальное число сопроцессора 8087, результат будет показан
    неверно.  Первая часть программы - удобное место для использования
    команды FXAM, которая определила бы тип числа в вершине стека.  Но
    в данном примере эта команда не используется, так как
    предполагается, что исходное число имеет нужный тип.
 
      Эта программа не приспособлена для оформления формата
    выводимого числа.  Результат всегда содержит знак (либо пробел,
    либо знак "-") и целую часть, состоящую из одной цифры.  После
    десятичной точки расположены восемь десятичных позиций, а затем
    буква E и три позиции цифр для степени 10.  Результат работы этой
    программы не так хорош, как можно было желать, но он позволяет
    видеть результат работы программы в читабельной форме.  Более
    красивое преобразование числа потребовало бы значительно больше
    команд, и лишь малая часть из них помогла бы пониманию работы
    сопроцессора 8087.
 
      Программа преобразования работает следующим образом.  Сначала
    она определяет порядок числа.  Например, число 1234 имеет порядок
    3; это означает, что оно находится между значениями 103 и 104.
    Найдя правильный порядок числа, программа сохраняет его значение
    (показатель степени результата) и делит исходное число на 10 в этой
    степени.  Теперь число находится в интервале от 1 до 10.  Затем
    программа умножает число на 108.  Запись этого числа в десятичной
    форме дает девять десятичных цифр; старшая цифра - целая часть,
    младшие восемь цифр - дробные разряды.

             Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:04:46
             Фиг. 7.25 Преобразование плавающего формата в текстовый       Page     1-1
 
                                           PAGE    ,132
                                           TITLE   Фиг. 7.25 Преобразование плавающего формата в текстовый
            0000                     CODE    SEGMENT PUBLIC
                                           ASSUME  CS:CODE,DS:CODE,ES:CODE
                                           EXTRN   TEN_TO_X:NEAR
            0000  ????               OLD_CW            DW      ?
            0002  ????               NEW_CW            DW      ?
            0004  ????               EXPONENT        DW      ?
            0006  ???????????????????      BCD_RESULT      DT      ?
                  ?
            0010  ???????????????????      BCD_EXPONENT    DT      ?
                  ?
            001A  00E1F505                 TEN8        DD      100000000
            001E  20 20 20 20 20 20 20     PRINT_STRING    DB      '         E    ',10,13,'$'
                  20 20 45 20 20 20 20
                  0A 0D 24
 
         Фиг. 7.25 Преобразование плавающего формата в текстовый (начало)
                                     PUBLIC  FLOAT_ASCII
                                     ;--------------------------------------------
                                     ; Эта программа извлекает из вершины стека
                                     ;  сопроцессора 8087 число и выводит его на
                                     ;  экран в плавающем формате.
                                     ; Параметры: число в ST(0)
                                     ; Результат: изображение числа на экране;
                                     ;  число извлечено из стека сопроцессора 8087
                                     ;--------------------------------------------
            002F                     FLOAT_ASCII     PROC    NEAR
                                                             ;-----ST(0)-----;-----ST(1)-----;--ST(2)--
                                                             ; X         ; ?         ; ?
            002F  9B D9 C0                       FLD     ST(0)           ; X         ; X         ; ?
            0032  9B D9 E1                       FABS              ; |X|             ; X         ; ?
            0035  9B D9 E8                       FLD1              ; 1         ; |X|             ; X
            0038  9B D9 C9                       FXCH    ST(1)           ; |X|             ; 1             ; X
            003B  9B D9 F1                       FYL2X                   ; LOG2(X)       ; X             ; ?
            003E  9B D9 E9                       FLDL2T                  ; LOG2(10)      ; LOG2(X)       ; X
            0041  9B DE F1                       FDIVRP  ST(1),ST(0)     ; E=LOGX/LOG10  ; X             ; ?
            0044  D9 3E 0000 R                   FNSTCW  OLD_CW          ;           ;           ;
            0048  9B                       FWAIT                   ;           ;           ;
            0049  A1 0000 R                MOV     AX,OLD_CW       ;           ;           ;
            004C  25 F3FF                        AND     AX,NOT 0C00H    ;           ;           ;
            004F  0D 0400                        OR      AX,0400H        ;           ;           ;
            0052  A3 0002 R                MOV     NEW_CW,AX       ;           ;           ;
            0055  9B D9 2E 0002 R                FLDCW   NEW_CW          ;           ;           ;
            005A  9B D9 FC                       FRNDINT                 ; I = INT(E)    ; X             ; ?
            005D  9B D9 2E 0000 R                FLDCW   OLD_CW          ;           ;           ;
            0062  9B DF 16 0004 R                FIST    EXPONENT        ; I         ; X         ; ?
            0067  9B D9 E0                       FCHS              ; -I        ; X         ; ?
            006A  E8 0000 E                CALL    TEN_TO_X        ; 10 ** (-I)    ; X           ; ?
            006D  9B DE C9                       FMULP   ST(1),ST(0)     ; X/10**I       ; ?             ; ?
            0070  9B DA 0E 001A R                FIMUL   TEN8            ; Мантисса      ; ?             ; ?
            0075  9B DF 36 0006 R                FBSTP   BCD_RESULT      ; ?         ; ?         ; ?
            007A  9B DF 06 0004 R                FILD    EXPONENT        ; I         ; ?         ; ?
            007F  9B DF 36 0010 R                FBSTP   BCD_EXPONENT    ; ?         ; ?         ; ?
                                     ;-----  Вывод на экран значений,запомненных как упакованные
                                     ;       целые двоично-десятичные числа
            0084  FC                       CLD
            0085  8D 3E 001E R                   LEA     DI,PRINT_STRING                   ; Указатель на выводимую
                                                                         ;  строку
            0089  A0 000F R                MOV     AL,BYTE PTR BCD_RESULT+9
            008C  E8 00C3 R                CALL    PRINT_SIGN                  ; Печать знака
            008F  A0 000A R                MOV     AL,BYTE PTR BCD_RESULT+4
            0092  E8 00DF R                CALL    PRINT_NYBBLE                ; Печать первой цифры
            0095  B0 2E                    MOV     AL,'.'                          ; Десятичная точка
            0097  AA                       STOSB
            0098  8D 1E 0009 R                   LEA     BX,BCD_RESULT+3
            009C  B9 0004                        MOV     CX,4                        ; Печать 8 байт (16 цифр)
            009F                     DO_BYTE:                            ;  после десятичной точки
            009F  E8 00CD R                CALL    PRINT_BYTE
            00A2  E2 FB                    LOOP    DO_BYTE
            00A4  B0 45                    MOV     AL,'E'                          ; Символ экспоненты
            00A6  AA                       STOSB
 
         Фиг. 7.25 Преобразование плавающего формата в текстовый (продолжение)
            00A7  A0 0019 R                MOV     AL,BYTE PTR BCD_EXPONENT+9
            00AA  E8 00C3 R                CALL    PRINT_SIGN                  ; Печать знака экспоненты
            00AD  A0 0011 R                MOV     AL,BYTE PTR BCD_EXPONENT+1
            00B0  E8 00DF R                CALL    PRINT_NYBBLE                ; Первая цифра экспоненты
            00B3  8D 1E 0010 R                   LEA     BX,BCD_EXPONENT
            00B7  E8 00CD R                CALL    PRINT_BYTE                  ; Оставшиеся цифры
            00BA  8D 16 001E R                   LEA     DX,PRINT_STRING
            00BE  B4 09                    MOV     AH,9H
            00C0  CD 21                    INT     21H                         ; Вывод всей строки на экран
            00C2  C3                       RET
            00C3                     FLOAT_ASCII     ENDP
 
                                     ;-----  Эта подпрограмма выводит ' ' или '+'
            00C3                     PRINT_SIGN      PROC    NEAR
            00C3  3C 00                    CMP     AL,0                 ; Проверка на знак
            00C5  B0 20                    MOV     AL,' '                 ; Занесение положительного знака
            00C7  74 02                    JZ      POSITIVE
            00C9  B0 2D                    MOV     AL,'-'                 ; Занесение минуса
            00CB                     POSITIVE:
            00CB  AA                       STOSB                         ; Сохранение в выводимой строке
            00CC  C3                       RET
            00CD                     PRINT_SIGN      ENDP
 
                                     ;-----  Эта программа печатает две десятичные цифры,
                                     ;       находящиеся в памяти по адресу [BX]
 
            00CD                     PRINT_BYTE      PROC    NEAR
            00CD  8A 07                    MOV     AL,[BX]              ; Выборка байта
            00CF  51                       PUSH    CX
            00D0  B1 04                    MOV     CL,4
            00D2  D2 E8                    SHR     AL,CL                ; Сдвиг старшей цифры
            00D4  59                       POP     CX
            00D5  E8 00DF R                CALL    PRINT_NYBBLE          ; Печать старшей цифры
            00D8  8A 07                    MOV     AL,[BX]              ; Выборка младшей цифры
            00DA  E8 00DF R                CALL    PRINT_NYBBLE          ; Печать младшей цифры
            00DD  4B                       DEC     BX              ; Переход на следующую пару цифр
            00DE  C3                       RET
            00DF                     PRINT_BYTE      ENDP
 
                                     ;-----  Печать одной десятичной цифры из регистра AL
 
            00DF                     PRINT_NYBBLE    PROC    NEAR
            00DF  24 0F                    AND     AL,0FH               ; Выделение младшей цифры
            00E1  04 30                    ADD     AL,'0'                 ; Преобразование в символ
            00E3  AA                       STOSB                         ; Сохранение в выводимой строке
            00E4  C3                       RET
            00E5                     PRINT_NYBBLE    ENDP
            00E5                     CODE    ENDS
                                           END
 
         Фиг. 7.25 Преобразование плавающего формата в текстовый (продолжение)
 
      Первая часть программы определяет правильный порядок исходного
    числа.  В программе логарифм числа по основанию 10 находится с
    помощью формулы
      Log10(X) = Log2(X)/Log2(10)
 
      Затем округляется порядок в направлении минус бесконечности,
    опять используя управление округлением.  В предыдущем примере,
    вычисляя 10X, мы пользовались умножением для переноса исходного
    числа в нужный диапазон.  Теперь мы используем константу TENB
    (которая содержит целое число 108) для того, чтобы вернуть число в
    нужный диапазон.  Наконец, команда FBSTP дважды преобразует числа в
    десятичное представление; сначала она дает нам девять цифр мантиссы
    числа, а затем - три цифры порядка.
 
      Остальная часть программы выполняет символьную обработку,
    необходимую для преобразования десятичного представления в строки
    символов.  Программа определяет и показывает знаки числа и порядка.
    Она распаковывает десятичные байты и преобразует их в символы;
    подпрограмма PRINT_BYTE делает распаковку, а подпрограмма
    PRINT_NYBBLE выполняте преобразование в символы.  Заметим, что в
    этом случае не нужна команда XLAT, так как все цифры имеют значения
    между 0 и 9.  (Но если исходное число - одно из неопределенных
    чисел, символьная строка будет содержать некоторые непонятные
    символы).
 
      Эта программа верно печатает любое число, лежащее в диапазоне
    длинных действительных чисел.  Любое число, выходящее за пределы
    возможностей этого представления (например 101234) имеет поле
    порядка, сокращенное до трех цифр.    Конечно, вы можете изменить
    программу так, чтобы она обрабатывала четыре цифры поля порядка,
    если вы этого желаете.  Но существует все же число, которое
    программой обрабатывается верно, но вы, возможно, пожелаетет
    изменить его изображение.  Если исходное число 0, результат
    печатается в виде 0.00000000E-932.    Так происходит потому, что поле
    порядка имеет смещение; процессор 8087 представляет число 0 с
    минимально возможным порядком (-4932) и с нулевой мантиссой.  Когда
    программа преобразует число в код ASCII, она верно печатает
    мантиссу и порядок (за исключением того, что ей приходится усекать
    порядок до трех цифр).  Если вы захотите обрабатывать такой порядок
    отдельно, то измените программу, вставив в нее проверку на нуль
    (чаще всего, с помощью команды FTST) в самом начале, рассматривая
    это, как специальный случай.

Квадратное уравнение


      Теперь приведем два примера, использующих программу индикации
    чисел с плавающей точкой.  Первый пример - решение квадратного
    уравнения.    Найдем корни уравнения, задаваемого следующей формулой
 
      0 = A*X**2 + B*X + C
 
      Из школьного курса математики известно, что решение этого
    уравнения
 
      X = ( -B +- SQR( B**2 - 4*A*C))/(2*A)
 
      Программа решения этого уравнения очевидна и показана на
    Фиг. 7.26.    В ней предполагается, что все три параметра A, B и C
    записаны в виде целых чисел.  Конечно, если вы будете использоваь
    программу не только как пример, нужно организовать процедуру ввода
    различных коэффициентов.
 

           Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:04:54
           Фиг. 7.26 Вычисление корней квадратного уравнения            Page   1-1
 
                                         PAGE    ,132
                                         TITLE   Фиг. 7.26 Вычисление корней квадратного уравнения
 
            0000                   STACK   SEGMENT STACK
            0000  0040[                  DW      64 DUP (?)
                  ????
                              ]
 
            0080                   STACK   ENDS
 
            0000                   CODE    SEGMENT
                                         ASSUME  CS:CODE,DS:CODE,ES:CODE
                                         EXTRN   FLOAT_ASCII:NEAR
            0000  0001             A           DW      1
            0002  FFFB             B           DW      -5
            0004  0006             C           DW      6
            0006  ????             STATUS  DW      ?
            0008  0004             FOUR    DW      4
            000A  0002             TWO     DW      2
            000C  8C AD A8 AC EB A5 20     ERROR_MSG       DB      'Мнимые корни',10,13,'$'
                AA AE E0 AD A8 0A 0D
                24
            001B                   QUADRATIC         PROC    FAR
            001B  1E                           PUSH    DS        ; Сохранение адреса возврата
            001C  2B C0                  SUB     AX,AX
            001E  50                           PUSH    AX
            001F  8C C8                  MOV     AX,CS
            0021  8E D8                  MOV     DS,AX
            0023  8E C0                  MOV     ES,AX
 
            0025  9B DB E3                     FINIT             ;-----ST(0)-----;-----ST(1)------
            0028  9B DF 06 0002 R              FILD    B               ; B         ; ?
            002D  9B D8 8E 0000                FMUL    ST(0)           ; B**2      ; ?
            0032  9B DF 06 0000 R              FILD    A               ; A         ; B**2
            0037  9B DE 0E 0008 R              FIMUL   FOUR      ; 4*A       ; B**2
            003C  9B DE 0E 0004 R              FIMUL   C               ; 4*A*C           ; B**2
            0041  9B DE E1                     FSUBRP  ST(1),ST(0)    ; D=B**2-4*A*C  ; ?
            0044  9B D9 E4                     FTST
            0047  9B DD 3E 0006 R              FSTSW   STATUS
            004C  9B                           FWAIT
            004D  8A 26 0007 R                 MOV     AH,BYTE PTR STATUS+1
            0051  9E                           SAHF
            0052  72 37                  JB      IMAGINARY
            0054  9B D9 FA                     FSQRT             ; SQR(D)          ;
 
          Фиг. 7.26 Вычисление корней квадратного уравнения (начало)
            0057  9B D9 C0                     FLD     ST(0)           ; SQR(D)          ; SQR(D)
            005A  9B D9 E0                     FCHS              ; -SQR(D)         ; SQR(D)
            005D  9B DE 06 0002 R              FIADD   B               ; B-SQR(D)      ; SQR(D)
            0062  9B D9 E0                     FCHS              ; -B+SQR(D)     ; SQR(D)
            0065  9B D9 C9                     FXCH    ST(1)           ; SQR(D)          ; -B+SQR(D)
            0068  9B DE 06 0002 R              FIADD   B               ; B+SQR(D)      ; -B+SQR(D)
            006D  9B D9 E0                     FCHS              ; N1=-B-SQR(D)  ; N2=-B+SQR(D)
            0070  9B DE 36 0000 R              FIDIV   A               ; N1/A      ; N2
            0075  9B DE 36 000A R              FIDIV   TWO       ; ROOT1=N1/2*A  ; N2
            007A  E8 0000 E                    CALL    FLOAT_ASCII     ; N2        ; ?
            007D  9B DE 36 0000 R              FIDIV   A               ; N2/A      ; ?
            0082  9B DE 36 000A R              FIDIV   TWO       ; ROOT2=N2/2*A  ; ?
            0087  E8 0000 E                    CALL    FLOAT_ASCII     ; ?         ; ?
            008A  CB                           RET
            008B                   IMAGINARY:
            008B  8D 16 000C R                 LEA     DX,ERROR_MSG
            008F  B4 09                  MOV     AH,9H
            0091  CD 21                  INT     21H       ; Вывод сообщения об ошибке
            0093  CB                           RET
            0094                   QUADRATIC         ENDP
            0094                   CODE    ENDS
                                         END     QUADRATIC
 
          Фиг. 7.26 Вычисление корней квадратного уравнения (продолжение)
 
      В примере отсутствует обработка комплексных чисел, но имеется
    проверка дискриминанта (B**2 - 4*A*C) на отрицательность, и если это
    число отрицательно, программа завешается с сообщением об ошибке.
    Можно было бы ввести в программу комплексную арифметику, тем не
    менее, ее нет в данном примере.  Необходимо помнить, что
    сопроцессор 8087 не обрабатывает автоматически комплексные или
    мнимые числа, и нужно писать программу раздельной обработки
    действительной и мнимой частей комплексного числа.
 
      Команда FTST проверяет дискриминант на отрицательность; она
    подобна сравнению с встроенным нулевым операндом источника.
    Программа записывает слово состояния в память, а затем загружает
    его в регистр флагов микропроцессора 8088.  После этого делается
    проверка JB (переход, если меньше), определяющая, меньше ли нуля
    дискриминант.  Оставшаяся часть программы проделывает работу по
    вычислению двух корней уравнения, и здесь используется то
    преимущество, что коэффициенты находятся в памяти.      Такой подход
    минимизирует объем используемого в сопроцессоре 8087 стека.  Но
    если вы переделаете эту программу так, что она будет подпрограммой,
    вызываемой из другой программы, вам, вероятно, захочется передавать
    параметры ей с помощью стека сопроцессора 8087.  В этом случае
    потребуется другой способ для загрузки подпрограммой некоторых
    величин.

Синус угла


      Последний пример использования сопроцессора 8087 - вычисление
    синуса угла.  У сопроцессора 8087 нет команды вычисления функции
    SIN; самое большее, что он может - это выполнить команду FPTAN,
    нахождение частичного тангенса.  Чтобы выполнить операцию SIN,
    воспользуемся этой командой, а также командой FPREM (частичный
    остаток).
 
      Программа, вычисляющая SIN, показана на Фиг. 7.27.  Эта
    программа вычисляет и печатает синусы углов от 1/2 до 6 с шагом 1/2
    радиана.  Выдача программы аналогична выдаче следующей программы на
    языке Бейсик:
 
      10  FOR X = .5 TO 6.0 STEP .5
      20  PRINT SIN(X)
      30  NEXT X
 
      Для печати результатов используется подпрограмма на Фиг. 7.25.
 

             Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:05:01
             Фиг. 7.27 Вычисление синуса угла                  Page     1-1
 
 
                                           PAGE    ,132
                                           TITLE   Фиг. 7.27 Вычисление синуса угла
 
            0000                     STACK   SEGMENT STACK
            0000  0040[                    DW      64 DUP (?)
                    ????
                              ]
 
            0080                     STACK   ENDS
            0000                     CODE    SEGMENT
                                           ASSUME  CS:CODE,DS:CODE,ES:CODE
                                           EXTRN   FLOAT_ASCII:NEAR
            0000  0001               NUM_ANGLE       DW      1
            0002  0002               DEN_ANGLE       DW      2
            0004  ????               STATUS  DW      ?
            0006  0004               FOUR    DW      4
            = 0040                         C3      EQU     40H
            = 0004                         C2      EQU     04H
            = 0002                         C1      EQU     02H
            = 0001                         C0      EQU     01H
            0008  93 A3 AE AB 20 E1 AB     ERROR_MSG       DB      'Угол слишком большой', 10, 13, '$'
                  A8 E8 AA AE AC 20 A1
                  AE AB EC E8 AE A9 0A
                  0D 24
            001F                     SIN     PROC    FAR
            001F  1E                       PUSH    DS
            0020  2B C0                    SUB     AX, AX
            0022  50                       PUSH    AX
            0023  8C C8                    MOV     AX, CS
            0025  8E D8                    MOV     DS, AX
            0027  8E C0                    MOV     ES, AX
            0029                     DO_AGAIN:
            0029  9B DB E3                       FINIT                   ;-----ST(0)-----;-----ST(1)------
            002C  9B DF 06 0000 R                FILD    NUM_ANGLE       ;           ;
            0031  9B DE 36 0002 R                FIDIV   DEN_ANGLE       ; X = Угол      ;
 
             Фиг. 7.27 (a) Процедура SIN (начало)
            0036  9B D9 EB                       FLDPI                   ; PI        ; X
            0039  9B DE 36 0006 R                FIDIV   FOUR            ; PI/4            ; X
            003E  9B D9 C9                       FXCH              ; X         ; PI/4
            0041  9B D9 F8                       FPREM                   ; R         ; PI/4
            0044  9B DD 3E 0004 R                FSTSW   STATUS
            0049  9B                       FWAIT
            004A  8A 26 0005 R                   MOV     AH, BYTE PTR STATUS+1
            004E  F6 C4 04                       TEST    AH, C2
            0051  75 55                    JNZ     BIG_ANGLE
            0053  F6 C4 02                       TEST    AH, C1          ; Определяется, необходимо ли вычитание PI/4
            0056  74 05                    JZ      DO_R            ; Если 0, то не необходимо вычитание PI/4
            0058  9B DE E1                       FSUBRP  ST(1), ST(0)    ; A = PI/4-R    ; ?
            005B  EB 06                    JMP     SHORT DO_FPTAN
            005D                     DO_R:
            005D  9B D9 C9                       FXCH              ; PI/4            ; R
            0060  9B D8 D9                       FCOMP                   ; R         ; ?
            0063                     DO_FPTAN:
            0063  9B D9 F2                       FPTAN                   ; OPP             ; ADJ   Где OPP/ADJ=Tan(A)
 
                                     ;-----  Опеределение того, что нужно - синус или косинус
 
            0066  F6 C4 42                       TEST    AH, C3 or C1
            0069  7A 03                    JPE     DO_SINE
            006B  9B D9 C9                       FXCH              ; ADJ             ; OPP
            006E                     DO_SINE:                ; D         ; N
 
                                     ;-----  Вычисление N/SQR(N**2 + D**2)
            006E  9B D8 8E 0000 U                FMUL    ST(0)           ; D**2            ; N
            0073  9B D9 C9                       FXCH    ST(1)           ; N         ; D**2
            0076  9B D9 C0                       FLD     ST(0)           ; N         ; N         ; D**2
            0079  9B D8 8E 0000 U                FMUL    ST(0)           ; N**2            ; N             ; D**2
            007E  9B DC 06 0000 U                FADD    ST(2)           ; N**2 + D**2   ; N             ; D**2
            0083  9B D9 FA                       FSQRT                   ; SQR(N2 + D2)  ; N             ; D**2
            0086  9B DE F1                       FDIVRP  ST(1)           ; SIN(X)        ; D**2
            0089  9B D9 C9                       FXCH    ST(1)           ; D**2            ; SIN(X)
            008C  9B D8 D9                       FCOMP                   ; SIN(X)        ; ?
            008F  F6 C4 01                       TEST    AH, C0
            0092  74 03                    JZ      SIGN_OK
            0094  9B D9 E0                       FCHS
            0097                     SIGN_OK:
            0097  E8 0000 E                CALL    FLOAT_ASCII
            009A  FF 06 0000 R                   INC     NUM_ANGLE
            009E  83 3E 0000 R 0D                CMP     NUM_ANGLE, 13
            00A3  77 02                    JA      RETURN_INST
            00A5  EB 82                    JMP     DO_AGAIN
            00A7                     RETURN_INST:
            00A7  CB                       RET
            00A8                     BIG_ANGLE:
            00A8  8D 16 0008 R                   LEA     DX, ERROR_MSG
            00AC  B4 09                    MOV     AH, 9H
            00AE  CD 21                    INT     21H
            00B0  CB                       RET
            00B1                     SIN     ENDP
            00B1                     CODE    ENDS
                                           END     SIN
             Фиг. 7.27 (a) Процедура SIN (продолжение)
            A>SIN
             4.79425539E-001
             8.41470985E-001
             9.97494987E-001
             5.98472144E-001
             1.41120008E-001
            -3.50783228E-001
            -7.56802495E-001
            -9.77530118E-001
            -9.58924275E-001
            -7.05540326E-001
            -2.79415498E-001
             2.15119988E-001
 
             Фиг. 7.27 (b) Вывод процедуры SIN
 
            Фиг. 7.27 Вычисление синуса угла
 
      В первой части программы происходит ее инициализация для работы
    в качестве файла типа .EXE.  Затем сопроцессор 8087 загружает два
    целых числа и делит их, формируя исходный угол.  Это - пример
    использования двух целых чисел для порождения числа с плавающей
    точкой (в данном случае 1/2), что нельзя сделать непосредственно с
    помощью ассемблера.
 
      Как вы помните из тригонометрии, синус - периодическая функция.
    То есть функция дает один и тот же результат в случае исходных
    чисел, различающихся ровно на 2*PI.  Поэтому первой задачей
    подпрограммы SIN является замена исходного угла соответствующим
    значением, лежащим в диапазоне
 
      0 <= X < 2*PI
 
      В команде FPTAN требуется, чтобы угол находился в диапазоне
 
      0 <= X < PI/4
 
      Это означает, что даже если угол и меньше 2*PI, мы должны
    уменьшить его еще, чтобы он удовлетворял ограничениям команды
    FPTAN.  К счастью, если исходный угол уменьшен до значения,
    меньшего PI/4, все еще можно определить верное значение
    тригонометрических функций.  Чтобы это сделать, надо знать, в каком
    месте исходного диапазона от 0 до 2*PI находился исходный угол.
 
      Нужное уменьшение угла выполняет команда FPREM.  Она не только
    вычисляет остаток, но и три младших бита частного, определяемого в
    течение процесса поиска остатка.  Эти три бита команда записывает в
    слово состояния.  Следовательно, хотя мы и уменьшили угол до
    значения одной восьмой исходного диапазона, все же можно определить
    октант, в который попадет угол.  Зная его, можно найти формулу
    вычисления синуса с помощью тригонометрических преобразований.
    Таблица на Фиг. 7.28 показывает связь между исходным октантом и
    методом вычисления синуса угла.  В таблице предполагается, что
    число R - это остаток от уменьшения исходного угла до значения
    меньше PI/4.  Номер октанта появляется в разрядах C3 = C1 = C0
    после выполнения команды FPREM.
 
      С помощью этой таблицы мы можем определить формулу вычислений,
    применяемую в каждом случае выполнения программы.  После загрузки
    значения угла в радианах программа загружает число и делит его на
    4, чтобы использовать в команде FPREM.  В этот момент
    "захватывается" слово состояния.  Если процесс поиска остатка не
    завершился на этом единственном шаге, это означает, что исходный
    угол был больше 2**64.  Следовательно, его значение настолько больше
    максимально возможного при вычислениях тригонометрических функций,
    что мы отбрасываем это число, как слишком большое.      Этого не
    происходит со значениями, выбранными в примере, но здесь для
    иллюстрации введена такая проверка.
 
            Октанты
        C0  C3    C1    Диапазон        SIN(X) = :
        -----------------------------------------------------
         0  0    0    0     PI/4  SIN(R)
         0  0    1    PI/4  PI/2  COS(PI/4-R)
         0  1    0    PI/2  3*PI/4      COS(R)
         0  1    1    3*PI/4      PI    SIN(PI/4-R)
         1  0    0    PI    5*PI/4      - SIN(R)
         1  0    1    5*PI/4      3*PI/2      - COS(PI4-R)
         1  1    0    3*PI/2      7*PI/4      - COS(R)
         1  1    1    7*PI/4      2*PI  - SIN(PI/4-R)
 
                  (R - остаток, 0<R<PI/4)
        -----------------------------------------------------
 
                 Фиг. 7.28 SIN(X) в восьми секторах
 
      Программа проверяет разряд C1 в регистре состояния, чтобы
    определить, должна ли она использовать остаток R, или его надо
    вычесть из PI/4.  Так как PI/4 еще находится в одном из регистров,
    это сделать просто.  Если вычитание не требуется, команда FCOMP
    удаляет из стека ненужное значение PI/4.
 
      Затем команда FPTAN вычисляет частичный тангенс.  Результат
    работы команды показан, как OPP/ADJ (сокращения от английских слов
    Opposite (противоположный) и Adjacent (соседний)), что равно
    тангенсу угла R или PI/4-R, в зависимости от того, что было
    выбрано.  С помощью этих двух чисел теперь можно опеределить синус
    или косинус угла.  Например, синус, заданный парой чисел OPP/ADJ,
    можно вычислить по формуле
 
      SIN(X) = OPP/SQR(OPP**2+ADJ**2), где TAN(X) = OPP/ADJ
 
      Чтобы вычислить косинус, нужно числитель заменить на ADJ.  Мы
    решаем, нужен ли синус или косинус, анализируя запомненные
    описатели октанта, т.е.  проверяя значения разрядов C3 и C1.
    Команда TEST выделяет эти значения, а команда JPE делает переход,
    если они оба нулевые или оба единичные.  В этом случае мы вычисляем
    синус; если же они различны, мы вычисляем косинус, что достигается
    заменой местами значений OPP и ADJ в стеке регистров.
 
      Далее следующие команды сопроцессора 8087 вычисляют значение
    синуса (или косинуса) по значению частичного тангенса.
    Единственный шаг, который еще надо выполнить - это определение
    окончательного знака результата.  В случае синуса результат
    отрицателен, если угол находится в октантах от четвертого до
    седьмого.  Проверка разряда C0 определяет верный знак результата.
    Затем программа FLOAT_ASCII, показанная на Фиг. 7.25, печатает
    число в плавающем формате.      Управление возвращается назад, к началу
    цикла, если еще не пройдены все октанты.  Нижняя часть Фиг. 7.27
    иллюстрирует результат выполнения этой программы.

Отладка программ с использованием 8087


      Перед тем, как мы окончим обсуждение процессора 8087, хотелось
    бы немного поговорить об отладке написанных для него программ.
    Проблема, с которой мы сталкиваемся здесь, заключается в том, что
    утилита DEBUG (отладчик) в DOS не поддерживает процессор 8087.
    Это означает, что при обнаружении отладчиком контрольной точки он
    не отображает на экране содержимого регистров сопроцессора 8087.
    Это сильно затрудняет отладку программы, изменяющей регистры
    сопроцессора 8087.
 
      В книге предлагается метод, который можно применять для отладки
    программ сопроцессора 8087, используя отладчик DOS.  Этот метод
    может быть и не наилучший, но он использовался при отладке программ
    приведенных в этой главе.
 
      Основное препятствие заключается в неспособности программы
    DEBUG показывать содержимое регистрового стека сопроцессора 8087.
    Без переписывания заново программы DEBUG этот метод дает
    существенную иинформацию, необходимую при отладке программы
    сопроцессора 8087.  Этот метод отладки требует, чтобы программа
    была написана, как отдельно выполняемая, либо как файл типа .EXE,
    либа типа .COM.  Даже если вы пишете подпрограмму, отладьте ее
    сначала, как головную программу.  Одной из первых команд программы
    должна быть команда FINIT, сбрасывающая процессор 8087 в состояние,
    в которое он попадает при включении питания.  Это надо сделать,
    чтобы можно было прогонять программу снова и снова, всегда начиная
    сначала.  Рассматриваемый здесь метод отладки не позволит вам
    остановиться, проанализироваь регистры сопроцессора 8087, а затем
    продолжить программу с этого же места.  Этот метод основывается на
    возможности начинать все сначала после каждой контрольной точки.
 
      Вы должны организовать все параметры подпрограммы в виде ячеек
    памяти, и программа должна загружать все эти числа в
    соответствующие регистры вслед за командой FINIT.  Это необходимо,
    даже если идет работа с программой, принимающей параметры,
    переданные через стековые регистры.  Сначала отлаживаемая программа
    работает с параметрами, лежащими в памяти.  После того, как
    арифметика и логика программы будет отлажена, можно будет изменить
    программу так, чтобы она принимала параметры из регистрового стека.
      Цель всех этих действий - позволить программе выполняться без
    внешнего вмешательства.  Это означает, что можно запустить
    программу сначала и выполнять ее до некоторой команды, и перезапуск
    программы приведет к точно такому же ее выполнению.  Такое свойство
    необходимо, так как предлагаемый метод индикации регистров
    разрушает содержимое стека процессора 8087, и когда это произошло,
    продолжать выполнение программы с этого же места уже нельзя.
    Программу надо перезапустить с самого начала и остановить ее уже в
    другом месте, а это возможно благодаря принятым мерам.  Последние
    два примера, квадратное уравнение и функция синуса, устроены именно
    таким образом:  их параметры находятся в памяти, и программы
    начинаются с команды FINIT.
 
      Следующий этап процедуры отладки требует размещения
    специального программного фрагмента в заранее фиксированном месте
    вашей программы.  Для отладки примеров был выбран адрес 200, так
    как ни один из этих примеров не занимает более 500 байт.  Этот
    программный фрагмент предназначен только для отладки, и вы удалите
    его перед получением окончательной версии программы.  Такой
    фрагмент показан на Фиг. 7.29.  Как вы видите, он очень короток и
    содержит только три команды и два поля данных.  Первое поле данных
    содержит константу, в данном случае 106, или 1000000.  Выбор этого
    значения остается за вами; другое значение может оказаться
    подходящим, если ваша программа работает с числами, меньшими 10-6,
    или большими 1012.
 
      Смысл этого программного фрагмента заключается в том, что он
    преобразует содержимое вершины стека в число, которое вы сможете
    увидеть.  Этот фрагмент умножает содержимое текущей вершины стека
    на число с массой нулей, а это эквивалентно сдвигу десятичной точки
    вправо.  В данном случае, если вершина стека содержит 1/2,
    умножение преобразует ее в 500000.
 
      После того, как число преобразовано в большое целое (вместо
    дробного), команда FBSTP записывает его в упакованной десятичной
    форме в поле, также находящееся в этом специальном программном
    фрагменте.    Затем команда INT 3 возвращает управление программе
    DEBUG.  Теперь можно использовать команду Display программы DEBUG,
    чтобы посмотреть на 10 байт, записанных командой FBSTP.  Конечно,
    читать показанное значение нужно наоборот, так как это - способ,
    которым сопроцессор 8087 записывает десятичные числа.  Также надо
    учесть модификацию десятичнлй точки, которую выполнило умножение.
 
      ------------------------------
      TEN6  DD    1000000
            ORG   200H
      BCD_TEMP    DT    ?
            ORG   210H
            FIMUL TEN6
            FBSTR BCD_TEMP
            INT   3
      --------------------------- Фиг. 7.29 Отладка процедуры
                              для числового сопроцессора
      Отладка программы для сопроцессора 8087 осуществляется
    следующим образом.  Как только вы решили, что программа работает
    неверно, вы находите место контрольной точки по листингу программы.
    Использование команды Unassemble многого не даст, так как все
    команды сопроцессора 8087 дезассемблируются как команды ESC.  Так
    что использование литсинга программы существенно.
 
      Теперь вы выполняете программу с начала до контрольной точки;
    именно для этого вы сконструировали программу так, чтобы ее можно
    было перезапустить с начала баз какой=либо подготовки.  Всякий раз,
    когда вы вновь устанавливаете контрольную точку, нужно выполнять
    программу с начала.
 
      Когда программа попадает на контрольную точку, управление
    передается в отладчик.  Теперь вы можете выполнить тот специальный
    фрагмент кода, который помещен в программу.  Команда INT 3 в конце
    этого фрагмента возвращает управление в программу DEBUG, так что вы
    можете увидеть, что за число находилось в вершине стека, когда
    выполнялась исходная контрольная точка.  Так как была использована
    команда FBSTP, она извлекла число из вершины стека, записав аго в
    память.  Поэтому, чтобы увидеть второе число стека, ST1, вы можете
    еще раз выполнить отладочный фрагмент; все это можно повторять
    столько раз, сколько вы хотите.  Когда по этому методу будет
    получено десятичное число, содержащее значение 0FFH как в старшем,
    так и в знаковом байтах, знайте, что из стека извлеклось пустое
    значение.  Далее в программе можно установить новую контрольную
    точку, и снова выполнить программу сначала.  Таким образом вы
    можете пройти путь по всей программе, пока не найдется место
    ошибки.  Как только ошибка найдена, можно либо исправить ее на
    месте ("залатать" ошибку), или выйти назад в DOS, чтобы
    отредактировать и заново ассемблировать программу.      Когда, наконец,
    программа выполняется верно, и вам больше уже не нужна программа
    DEBUG, можно удалить отладочный фрагмент из программы.  В этот же
    момент вы можете изменить программу так, чтобы принимать параметры
    через регистры стека, а не через память.