Сопроцессор использует шесть основных типов арифметических команд:
- Fxxx
Первый операнд берется из верхушки стека (источник), второй - следующий элемент стека. Результат выполнения команды записывается в стек
- Fxxx память
Источник берется из памяти, приемником является верхушка стека ST(0). Указатель стека ST не изменяется, команда действительна только для операндов с одинарной и двойной точностью
- Fixxx память
Аналогично предыдущему типу команды, но операндами могут быть 16- или 32-разрядные целые числа
- Fxxx ST, ST(i)
Для этого типа регистр ST(i) является источником, а ST(0) - верхушка стека - приемником. Указатель стека не изменяется
- Fxxx ST(i), ST
Для этого типа регистр ST(0) является источником, а ST(i) - приемником. Указатель стека не изменяется
- FxxxP ST(i), ST
Регистр ST(i) - приемник, регистр ST(0) - источник. После выполнения команды источник ST(0) извлекается из стека
Строка "xxx" может принимать следующие значения:
- ADD - Сложение
- SUB - Вычитание
- SUBR - Обратное вычитание, уменьшаемое и вычитаемое меняются местами
- MUL - Умножение
- DIV - Деление
- DIVR - Обратное деление, делимое и делитель меняются местами
Кроме основных арифметических команд имеются дополнительные арифметические команды:
- FSQRT - Извлечение квадратного корня
- FSCALE - Масштабирование на степень числа 2
- FPREM - Вычисление частичного остатка
- FRNDINT - Округление до целого
- FXTRACT - Выделение порядка числа и мантиссы
- FABS - Вычисление абсолютной величины числа
- FCHS - Изменение знака числа
По команде FSQRT вычисленное значение квадратного корня записывается в верхушку стека ST(0).
Команда FSCALE изменяет порядок числа, находящегося в ST(0). По этой команде значение порядка числа ST(0) складывается с масштабным коэффициентом, который должен быть предварительно записан в ST(1). Действие этой команды можно представить следующей формулой:
ST(0) = ST(0) * 2n, где -215 <= n <= +215
В этой формуле n - это ST(1).
Команда FPREM вычисляет остаток от деления делимого ST(0) на делитель ST(1). Знак результата равен знаку ST(0), а сам результат получается в вершине стека ST(0).
Действие команды заключается в сдвигах и вычитаниях, аналогично ручному делению "в столбик". После выполнения команды флаг C2 регистра состояния может принимать следующие значения:
- 0 - Остаток от деления, полученный в ST(0), меньше делителя ST(1), команда завершилась полностью
- 1 - ST(0) содержит частичный остаток, программа должна еще раз выполнить команду для получения точного значения остатка
Команда RNDINT округляет ST(0) в соответствии с содержимым поля RC управляющего регистра.
Команда FABS вычисляет абсолютное значение ST(0). Аналогично, команда FCHS изменяет знак ST(0) на противоположный.
Трансцендентные команды
Трансцендентные команды предназначены для вычисления следующих функций:
- тригонометрические (sin, cos, tg,...)
- обратные тригонометрические (arcsin, arccos,...)
- показательные (xy, 2x, 10x, ex)
- гиперболические (sh, ch, th,...)
- обратные гиперболические (arsh, arch, arcth,...)
Вот список всех трансцендентных команд математического сопроцессора:
- FPTAN Вычисление частичного тангенса
- FPATAN Вычисление частичного арктангенса
- FYL2X Вычисление y*log2(x)
- FYL2XP1 Вычисление y*log2(x+1)
- F2XM1 Вычисление 2x-1
- FCOS Вычисление cos(x)
- FSIN Вычисление sin(x)
- FSINCOS Вычисление sin(x) и cos(x) одновременно
Команда FPTAN вычисляет частичный тангенс ST(0), размещая в стеке такие два числа x и y, что y/x = tg(ST(0)).
После выполнения команды число y располагается в ST(0), а число x включается в стек сверху (то есть записывается в ST(1)). Аргумент команды FPTAN должен находится в пределах:
0 <= ST(0) <= pi/4
Пользуясь полученным значением частичного тангенса, можно вычислить другие тригонометрические функции по следующим формулам:
- sin(z) = 2*(y/x) / (1 + (y/x)2)
- cos(z) = (1 - (y/x)2) / (1 + (y/x)2)
- tg(z/2) = y/x;
- ctg(z/2) = x/y;
- cosec(z) = (1 + (y/x)2) / 2*(y/x)
- sec(z) = (1 + (y/x)2) / (1 - (y/x)2)
Где z - значение, находившееся в ST(0) до выполнения команды FPTAN, x и y - значения в регистрах ST(0) и ST(1), соответственно.
Команда FPATAN вычисляет частичный арктангенс:
z=arctg(ST(0)/ST(1))=arctg(x/y).
Перед выполнением команды числа x и y располагаются в ST(0) и ST(1), соответственно. Аргументы команды FPATAN должен находится в пределах:
0 < y < x
Результат записывается в ST(0).
Команда FYL2X вычисляет выражение y*log2(x), операнды x и y размещаются, соответственно, в ST(0) и ST(1). Операнды извлекаются из стека, а результат записывается в стек. параметр x должен быть положительным числом.
Пользуясь результатом выполнения этой команды, можно вычислить следующим образом логарифмические функции:
- Логарифм по основанию два: log2(x) = FYL2(x)
- Натуральный логарифм: loge(x) = loge(2) * log2(x) = FYL2X(loge(2), x) = FYL2X(FLDLN2, x)
- Десятичный логарифм: log10(x) = log10(2) * log2(x) = FYL2X (log10(2), x) = FYL2X(FLDLG2, x)
Функция FYL2XP1 вычисляет выражение y*log2(x+1), где x соответствует ST(0), а y - ST(1). Результат записывается в ST(0), оба операнда выталкиваются из стека и теряются.
На операнд x накладывается ограничение: 0 < x < 1 - 1/sqrt(2)
Команда F2XM1 вычисляет выражение 2x-1, где x - ST(0). Результат записывается в ST(0), параметр должен находиться в следующих пределах: 0 <= x <= 0,5
Команда FCOS вычисляет cos(x). Параметр x должен находиться в ST(0), туда же записывается результат выполнения команды.
Команда FSIN аналогична команде FCOS, но вычисляет значение синуса ST(0).
Команда FSINCOS вычисляет одновременно значения синуса и косинуса параметра ST(0). Значение синуса записывается в ST(1), косинуса - в ST(0).
Константы FPU
FLD1 - Поместить в стек 1,0
FLDZ - Поместить в стек +0,0
FLDPI - Поместить в стек число π
FLDL2E - Поместить в стек log2(e)
FLDL2T - Поместить в стек log2(10)
FLDLN2 - Поместить в стек ln(2)
FLDLG2 - Поместить в стек lg(2)
Все эти команды помещают в стек (то есть уменьшают ТОР на один и помещают в ST(0)) соответствующую часто используемую константу.
Пример
Пример вычисления выражения:
Анализ особенностей задачи.
Возможны две ситуации деления на ноль:
• , если . Например, .
• , если . Например, .
В то же время ситуация, когда , является допустимой.
.Model Small
.586
.Data
res dq 0
a dq 3.0
b dq 10.0
const25 dw 25
const4 dw 4
const5 dw 5
status dw 0
.Code
;Вывод вещественного числа
; аргумент - количество цифр дробной части
length_frac Equ [BP+4]
; локальные переменные
ten Equ word ptr [BP-2]
temp Equ word ptr [BP-4]
OutFloat Proc Near
ENTER 4, 0; выделим в кадре стека 4 байта под локальные переменные
MOV ten, 10
ftst; определяем знак числа
fstsw AX
SAHF
JNC @positiv
MOV AL, '-'; если число отрицательное - выводим минус
INT 29h
fchs; и получаем модуль числа
@positiv:
fld1; загружаем единицу
fld st(1); копируем число на вершину стека
fprem; выделим дробную часть
fsub st(2), st; отнимем ее от числа - получим целую часть
fxch st(2); меняем местами целую и дробную части
XOR CX, CX; обнуляем счетчик
; далее идет стандартный алгоритм вывода целого числа на экран
@1:
fidiv ten; делим целую часть на десять
fxch st(1); обменяем местами st и st(1) для команды fprem
fld st(1); копируем результат на вершину стека
fprem; выделим дробную часть (цифру справа от целой части)
fsub st(2), st; получим целую часть
fimul ten; *10
fistp temp; получаем очередную цифру
PUSH temp; заталкиваем ее глубже в стек
INC CX; и увеличим счетчик
fxch st(1); подготовим стек к следующему шагу цикла (полученное частное на вершину, в st(1) - 1)
ftst; проверим не получили ли в частном 0?
fstsw AX
SAHF
JNZ @1; нет - продолжим цикл
@2:; извлекаем очередную цифру, переводим её в символ и выводим.
POP AX
ADD AL, '0'
INT 29h
LOOP @2
; далее то же самое, только для дробной части. Алгоритм похож на вывод целого числа, только вместо деления умножение и проход по числу слева
fstp st; сначала проверим, есть ли дробная часть
fxch st(1)
ftst
fstsw AX
SAHF
JZ @quit; дробная часть отсутствует
MOV AL, '.'
INT 29h; если присутствует - выведем точку
MOV CX, length_frac; помещаем в счетчик длину дробной части
@3:
fimul ten; умножим на 10
fxch st(1); подготовка для fprem - меняем st и st(1) местами и
fld st(1); копируем число на вершину
fprem; отделим дробную часть от целой
fsub st(2), st; и оставляем дробную
fxch st(2)
fistp temp; выталкиваем полученное число из стека в temp
MOV AX, temp; по дробной части идем слева, значит число выводим сразу, без предварительного сохранения в стек
OR AL, 30h; перевод в ascii
INT 29h; на экран
fxch st(1); подготовим стек к следующему шагу цикла (полученное частное на вершину, в st(1) - 1)
ftst
fstsw AX
SAHF; проверим на 0 остаток дробной части
LOOPNE @3
@quit:
fstp; готово. Чистим стек сопроцессора
fstp st
LEAVE; эпилог
RET 2
OutFloat EndP
func Proc Far
;PUSHA
finit; инициализация сопроцессора
fld qword ptr[b]; b
fld qword ptr[a]; a b
fcom st(1); сравниваем a и b
fstsw status; сохраняем регистр флагов сопроцессора
MOV AH, byte ptr [status+1]
SAHF; записываем в регистр флагов процессора
JA a_bigger; переход если a больше
JB b_bigger; переход если b больше
; если равны
fild const25; 25 a b
JMP endcalc
a_bigger: ftst; сравнение a с 0
fstsw status; сохраняем регистр флагов сопроцессора
MOV AH, byte ptr [status+1]
SAHF; записываем в регистр флагов процессора
JE error; переход если a=0
fdivp st(1), st(0); b/a
fild const4; 4 b/a
fsubp st(1), st(0); b/a-4
JMP endcalc
b_bigger: fldz; 0 a b
fcomp st(2); сравнение b с 0
; a b
fstsw status; сохраняем регистр флагов сопроцессора
MOV AH, byte ptr [status+1]
SAHF; записываем в регистр флагов процессора
JE error; переход если b=0
fld st(0); a a b
fmul st(1), st(0); a a*a b
fmulp st(1), st(0); a*a*a b
fild const5; 5 a*a*a b
fsubp st(1), st(0); a*a*a-5 b
JMP endcalc
error:
fldz; формируем результат ошибки
endcalc:
fstp res; сохранение результата
RET
func EndP
start:
MOV AX, @data
MOV DS, AX
CALL func
XOR AX,AX
XOR BX,BX
XOR CX,CX
XOR DX,DX
finit; инициализируем сопроцессор
fld res; записываем число стека для вывода
PUSH 10; заталкиваем число знаков после запятой
CALL outfloat
MOV AX,4c00h
INT 21h
End start