Глава 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, можно удалить отладочный фрагмент
из программы. В этот же
момент вы можете изменить программу так,
чтобы принимать параметры
через регистры стека, а не через память.
Степени десяти
Первый пример - исходный текст программы на Фиг. 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, можно удалить отладочный фрагмент из программы. В этот же
момент вы можете изменить программу так, чтобы принимать параметры
через регистры стека, а не через память.