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

Второй пример использования сопроцессора 8087 гораздо глубже раскрывает перед нами его работу. Этот пример - подпрограмма, которая будет использоваться в дальнейшем. Подпрограмма предполагает, что исходное число находится в вершине стека; после возврата из подпрограммы в вершине стека находится число, равное десяти в степени X. Исходный текст этой подпрограммы приведен на Фиг. 7.24. Сопроцессор 8087 не имеет команды возведения 10 в произвольную степень, но мы можем возводить в любую степень двойки. Поэтому нужжно пользоваться формулой


10**X = 2**(X*Log2(10))

Первые две команды программы формируют показатель степени двух. Программа загружает константу Log210, а затем умножает ее на исходное число X, давая необходимую степень 2, называемую здесь E. Поле комментариев в примере используется для иллюстрации элементов стека сопроцессора 8087. Символ "?" означает, что значение соответствующего элемента стека неопределено. В подпрограмме используется всего три элемента стека, так что в поле комментариев есть три колонки. Хотя теперь мы имеем нужную степень двух у сопроцессора 8087 отсутствует команда, завершившая бы всю работу за один шаг. Команда F2XM1 возводит 2 в степень X, но только если X меньше или равен 1/2. Это означает, что степень E мы должны разделить на целую и дробную части; затем команда FSCALE сможет возвести 2 в целую степень, а команда F2XM1 обработает дробную часть.


Microsoft (R) Macro Assembler Version 5.00                  1/1/80 04:03:56
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              FNSTC     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

Перед разделением 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 перед возвратом из подпрограммы.