7.8.5. Синус угла

Последний пример использования сопроцессора 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:03:56
Фиг. 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 = Угол      ;
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          ;      ; 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)  ;      ; 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

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