|
|
|
|
Функция масштабирования вещественного в десятичное
Введя числа в NPX, мы можем производить вычисления над ними.
Если мы выходим из комнаты, то можем сохранить их в общей памяти
как временные вещественные (это делает команда FSTP). Но что делать, когда надо взглянуть на результаты? Что надо сделать для
того, чтобы преобразовать вещественное число с плавающей запятой
в целое число, состоящее из двух частей?
Ответ заключается в том, что мы должны поработать со смещенной экспонентой NPX таким образом, чтобы NPX выдало нам целую
мантиссу. При сохранении числа как строки упакованного BCD команда FBSTP сначала округляет число к ближайшему целому. Если число
слишком велико для представления строкой упакованного BCD, NPX не
может сохранить это число. Если число слишком мало, то точность
мантиссы при округлении числа теряется. Для того, чтобы применить
команду FBSTP, мы должны сначала убедиться в том, что число, хранящееся в регистре, лежит в соответствующем диапазоне.
Мы можем сказать, что число лежит в соответствующем диапазоне, так как его смещенная экспонента (двоичная десятичная запятая) имеет значение меньше, чем 64 (в противном случае число
слишком велико) и больше, чем количество двоичных цифр мантиссы
(в противном случае мы теряем точность). Обычно мы выбираем число, которое нам даст хорошую точность. Для числа, которое мы хотим иметь с точность до 10 десятичных цифр, значение экспоненты
32 является хорошим значением. Это означает, что двоичная десятичная запятая находится в бите 32, примерно на половине пути до
конца плавающей запятой. Не слишком большое и не слишком маленькое.
Как быть, если число имеет экспоненту, не лежащую в этом диапазоне? Мы должны поменять экспоненту. Первым шагом является определение того, какую экспоненту надо реально использовать. Мы
применяем команду FXTRACT, которая разбивает регистр данных NPX
на два, один из которых содержит мантиссу с нулевой экспонентой
(ST), а другой содержит правильную экспоненту первоначального
числа как вещественное число (ST(1)). Мы интересуемся регистром
ST(1).
Первым шагом вычисления является определение того, сколько
двоичных десятичных мест мы убрали. Иными словами, мы хотим определить расстояние между желаемой и существующей экспонентой. Команда FSUB может сказать нам это очень быстро.
Зная расстояние, можем ли мы использовать его в качестве коэффициента масштабирования (с помощью FSCALE) для первоначальной
экспоненты? Нет, потому что при отображении числа в экспоненциальном формате нам надо показать пользователю экспоненту в виде
а мы не можем это сделать, если экспонента является степенью числа 2. Идея этого упражнения заключается в том, чтобы NPX выдало
целое число и затем узнать, на сколько степеней числа 10 это число было сдвинуто для получения целого. Привести в порядок экспоненциальный формат.
Нам нужно каким-то образом преобразовать расстояние, которое
в настоящее время выражает степень числа 2, в расстояние целых
степеней числа 10. Оказывается, взаимоотношение между двумя значениями выражается следующим правилом:
2(X) = 10(X * log 2)
2(X) = 10(X * log 10 по основанию 2)
|
Второе взаимоотношение является результатом тождества:
log b по основанию a = 1/log a по основанию b
|
Не смотря на то, какой способ мы использовали, мы определили
значение Х (для выражения 10 в степени Х), требуемое для создания
соответствующего коэффициента масштабирования. Мы можем создать
коэффициент, применяя команды FLDLG2 (логарифм числа 2 по основанию 10) и FMUL, или команды FLDL2T (логарифм числа 10 по основанию 2) FDIV. Тем не менее, эти методы дают точное значение Х для
числа 10 в степени Х и нам необходимо ближайшее целое. Поэтому мы
применим команду FRNDINT для округления числа и получим нашу экспоненту по основанию 10.
Имея экспоненту, мы возводим 10 в степень Х (с помощью команды EXP10) и получаем коэффициент масштабирования для перевода вещественного числа в целое (с помощью FMUL). Экспонента 10 возвращается командой FIST (сохранить целое) и мантиссу командой FBSTP
(сохранить упакованный BCD). Все, за исключением сохранения BCD,
содержится в программе FLT2DEC, представленной в листинге 10-2.
Другой полезной хитростью является то, что однажды записав в
память число в формате упакованного BCD, мы можем использовать
программу отображения двоичного кода в шестнадцатиричном (например, BIN2HEX) для вывода на экран цифр, потому что они очень похожи на шестнадцатиричное число.
Мы говорили о листинге 10-2, и вот, наконец, подошла его очередь. Обратите внимание, что как и в программе DUMP87, она также
сформатирована для использования в качестве библиотеки. Кроме того, все операции выполняются в стеке главного центрального процессора или в ячейках, определенных вызывающим оператором, поэтому проблем с переносимостью не возникнет.
Листинг 10-2. DE2FLT, FLT2DEC и программы экспоненты
EXP2, EXP10, EXPE и EXRY
______________________________________________________________
PAGE 60,132 ; широкий листинг
#.8087 ; разрешить трансляцию команд 8087 NPX
;
PUBLIC dec2flt ; определить библиотечную программу
PUBLIC flt2dec ; определить библиотечную программу
PUBLIC exp10 ; определить библиотечную программу
PUBLIC expE ; определить библиотечную программу
PUBLIC expY ; определить библиотечную программу
PUBLIC exp2 ; определить библиотечную программу
;
;=============================================================
; Р Е А Л И З А Ц И Я
;
#.MODEL SMALL
;
#.CODE
;
;*************************************************************
; DEC2FLT - Преобразует целое десятичное с экспонентой в
; вещественное число с плавающей запятой. Записывает
; экспоненту и указатель в строку упакованного BCD в
; стеке. Возвращает результат в ST(0).
;
; Использовать: push offset (tbyte ptr packed_BCD)
; push exponent
; call dec2flt
;
; Требования: 3 ячейки стека
; Обозначения: N ..... экспонента для 10**N
; S ..... мантисса вещественного числа
;-------------------------------------------------------------
;
#.DATA
D2FLTD STRUCT
d2fltbp dw ? ; прежний базовый указатель
dw ? ; возвратить адрес
d2fltex dw ? ; экспонента
d2fltpd dw ? ; указатель для упакованного BCD
D2FLTD ENDS
;
#.CODE
dec2flt PROC NEAR
push bp
mov bp,sp ; параметры адреса
cmp word ptr [bp].d2fltex,0 ; проверить знак
; ... экспоненты
jz d2flt_npx ; если ноль, то 10**N
; ... не нужно
pushf ; сохранить знак экспоненты
jg d2flt_pos; если положительное,
; ... начать 10**N
neg word ptr [bp].d2fltex ; в противном случае
; ... сделать экспоненту положительной
d2flt_pos:
FILD word ptr [bp].d2fltex ; получить экспоненту 10
call exp10 ; вычислить 10**N
d2flt_npx: ; войти сюда, если экспонента 0
push si
mov si,[bp].d2fltpd ; получить указатель
; ... упакованного BCD
FBLD tbyte ptr [si] ; ST => S; ST(1) = 10**N
pop si
popf ; восстановить знак экспоненты
jz d2flt_end ; выполнено, если экспонента 0
jl d2flt_neg ; если отрицательная, разделить
FMUL; ST => мантисса * 10**N
jmp d2flt_end ; и выполнено
d2flt_neg:
FDIVR ; ST => мантисса / 10**N
d2flt_end:
pop bp ; восстановить bp
ret 4
dec2flt ENDP
;
;*************************************************************
; FLT2DEC - Преобразует вещественное число с плавающей запятой
; в целое с экспонентой. ST(0) содержит преобразуемое
: число. Стек содержит количество требуемых двоичных
; цифр и указатель расположения экспоненты 10.
; Результат возвращается ST(0), преобразованный в целое,
; и пишет экспоненту в указанном месте.
;
; Использовать: push sig_digits
; push offset (word ptr to exponent)
; call flt2dec
;
; Требования: 4 ячейки стека
; Обозначения: R ..... Отображаемое вещественное число
; N ..... Экспонента 10 для перевода R в целое
; I ..... Целая часть результата
; n(N) .. Ближайшее к N целое
;-------------------------------------------------------------
;
#.DATA
F2DECD STRUC
f2deccw dw ? ; оригинальное слово управления
f2decbp dw ? ; прежний базовый указатель
dw ? ; адрес возврата
f2decex dw ? ; указатель экспоненты
f2decsd dw ? ; количество значащих двоичных цифр
F2DECD ENDS
;
#.CODE
;* проверить управление округлением сейчас - применить другой?
F2DECCT EQU 03BFh ; новое слово управления - округлить
; ... ближайшее
;
flt2dec PROC NEAR
;
; Установить слово управления NPX и открыть запись в стек:
push bp ; сохранить прежний базовый указатель
STKADJ1 EQU f2decbp-F2DECD
sub sp,STKADJ1 ; сделать запись в стеке
mov bp,sp ; адрес новой структуры
push ax ; сохранить AX
mov ax,F2DECCT ; поместить новое слово управления
; ... в стек
push ax
FSTCW word ptr [bp].f2deccw
FLDCW word ptr [bp-4] ; установить округление к
; ... ближайшему целому
pop ax ; очистить стек
pop ax ; восстановить AX
;
; Найти N для 10**N в целях преобразования в целое:
FLD ST(0) ; продублировать R (сохранить до конца)
FXTRACT ; ST(1) => экспоненциальная часть R
FSTP ST(0) ; ST => экспоненциальная часть R
FISUBR word ptr [bp].f2decsd ; значащие ...
; ... цифры - экспонента = # цифр масштаба
FLDL2T ; ST => log2 (10), ST(1) => масштаб
FDIV ; ST => масштаб / log2 (10) = N
FRNDINT ; ST => n(N)
;
; Сохранить nint(N) как экспоненту и вычислить 10**nint(N)
push si
mov si,[bp],f2decex ; получить указатель экспоненты
FIST word ptr [si] ; сохранить масштаб основания 10
FWAIT
neg word ptr [si] ; указание двигать десятичную
; ... запятую
pop si
call exp10 ; вычислить 10**N (масштаб)
;
; В ST(1) теперь находится R (первоначальное вещественное
; число) - масштабировать его:
FMUL ; ST => R * 10**N = целое
FLDCW word ptr [bp].f2deccw ; восстановить слово
; ... управления
STKADJ2 EQU f2decbp-F2DECD
add sp,STKADJ2 ; восстановить первоначальный стек
pop bp ; восстановить базовый указатель
ret 4 ; очистить стек для возврата
flt2dec ENDP
;
;*************************************************************
; EXP10 - Возводит число 10 в степень ST(0).
; Возвращает результат в ST(0).
;
; Использует формулу: 10**N = 2**(N*log2(10))
;
; ВЫЗЫВАЕТ: EXP2
;
; Требования: 3 ячейки стека
; Обозначения: N ...... экспонента для 10**N
; X ...... эквивалентная экспонента для 2**X
; n(x) ... ближайшее к Х целое
; f(x) ... дробная часть Х
;-------------------------------------------------------------
exp10 PROC NEAR
FLDL2T ; ST > log2 (10); ST(1) => N
FMUL ; ST => N * log2 (10) => X
call exp2 ; возвести 2 в степень ST
ret ; ... для 10**N
exp10 ENDP
;
;*************************************************************
; EXPE - Возводит число E в степень ST(0).
; Возвращает результат в ST(0).
;
; Использует формулу: E**N = 2**(N*log2(E))
;
; ВЫЗЫВАЕТ: EXP2
;
; Требования: 3 ячейки стека
; Обозначения: N ...... экспонента для E**N
; X ...... эквивалентная экспонента для 2**X
; n(x) ... ближайшее к Х целое
; f(x) ... дробная часть Х
;-------------------------------------------------------------
expE PROC NEAR
FLDL2E ; ST > log2 (e); ST(1) => N
FMUL ; ST => N * log2 (e) => X
call exp2 ; возвести 2 в степень ST
ret ; ... для E ** N
expE ENDP
;
;*************************************************************
; EXPY - Возводит Y [ST(0)] в степень N [ST(1)]
; Возвращает результат в ST(0)
; ST(1) (значение N) теряется!
;
; Использует формулу: Y**N = 2**(N*log2(Y))
;
; **** ПРИМЕЧАНИЕ: Y ДОЛЖНО БЫТЬ ПОЛОЖИТЕЛЬНОЕ ****
;
; ВЫЗЫВАЕТ: EXP2
;
; Требования: 3 ячейки стека
; Обозначения: N ...... экспонента для Y**N
; X ...... эквивалентная экспонента для 2**Y
; n(x) ... ближайшее к Х целое
; f(x) ... дробная часть Х
;-------------------------------------------------------------
expY PROC NEAR
FYL2X ; ST => N * log2 (Y); (Y) => X
call exp2 ; возвести 2 в степень ST
ret ; ... для Y ** N
expY ENDP
;
;*************************************************************
; EXP2 - Возводит число 2 в степень ST(0).
; Возвращает результат в ST(0).
;
; Требования: 3 ячейки стека
; Обозначения: X ...... экспонента для 2**X
; n(x) ... ближайшее к Х целое
; f(x) ... дробная часть Х
;-------------------------------------------------------------
;
#.DATA
EXP2D STRIC
exp2cc dw ? ; коды условий
exp2cw dw ? ; оригинальное слово управления
exp2bp dw ? ; прежний базовый указатель
dw ? ; адрес возврата
EXP2D ENDS
;
#.CODE
EXP2CT EQU 03BFh ; новое слово управления - округлять к
; ... ближайшему
exp2 PROC NEAR
;
; Установить слово управления NPX и открыть запись в стек:
push bp ; сохранить прежний базовый указатель
STKADJ3 EQU exp2bp-EXP2D
sub sp,STKADJ3 ; сделать запись в стеке
mov bp,sp ; адрес новой структуры
push ax ; сохранить AX
mov ax,EXP2CT ; поместить новое слово управления
; ... в стек
push ax
FSTCW word ptr [bp].exp2cw
FLDCW word ptr [bp-4] ; установить округление к
; ... ближайшему целому
pop ax ; очистить стек
pop ax ; восстановить AX
;
; Начать обработку числа:
FLD ST(0) ; ST => ST(1) => X для 2**X
FRNDINT ; ST => n(N); ST(1) => X
FXCH ; ST => X; ST(1) => n(N)
FSUB ST,ST(1) ; ST => f(X); ST(1) = n(X)
FTST ; установить коды условий
FSTSW word ptr [bp].exp2cc ; сохранить коды условий
FWAIT
and byte ptr [bp+1].exp2cc,45h ; замаскировать все
; ... кроме кодов условий
cmp byte ptr [bp+1].exp2cc,1 ; проверить на
; ... отрицательность
ja exp2_err ; NAN или бесконечность -> ошибка
je exp2_neg ; дробная часть минусовая
;
F2XM1 ; ST => (2**f(X)) - 1; ST(1) = n(X)
FLD1 ; ST => 1; ST(1) => (2**f(X))-1;
; ... ST(2) = n(X)
FADD ; ST => 2**f(X); ST(1) => n(X)
FSCALE ; ST => 2**(X) => 2**(N(log2(?)) => ?**N
FSTP; ST => ?**N; ST(1) => восстановлен
jmp exp2_mer ; соединить
;
exp2_neg:
FABS; ST => 1-f(x); ST(1) = n(X) + 1
F2XM1 ; ST => (2**(1-f(x)))-1; ST(1) = n(X) + 1
FLD1; ST => 1; ST(1) => (2**(1-f(x)))-1
FADD; ST => 2**(1-f(x)); ST(1) => n(X) +1
FXCH; ST => n(X) + 1; ST(1) => 2**(1-f(x))
FLD1; ST => 1; ST(1) = n(X) + 1
FSCALE ; ST => 2**(n(X) + 1);
; ... ST(2) => 2**(1-f(x))
FDIRP ST(2),ST ; ST(1) => 2**(n(X) + 1)/2**(1 - f(x))
FSTP ST(0) ; ST => 2**(n(x) + 1 - 1 + f(x) => 2**(X)
;
exp2_mer:
clc ; нет ошибок
exp2_out:
FLDCW word ptr [bp].exp2cw ; восстановить слово
; ...состояния
STKADJ4 EQU exp2bp-EXP2D
add sp,STKADJ4 ; восстановить первоначальный стек
pop bp ; восстановить базовый указатель
ret
exp2_err:
stc ; были ошибки
jmp exp2_out
exp2 ENDP
;*************************************************************
END ; конец программ
______________________________________________________________
|
|
|