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