Второй пример использования сопроцессора 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 перед возвратом из
подпрограммы.