На главную
Подписка
Новости










Главная / MS-DOS / MS-DOS. РУКОВОДСТВО РАЗРАБОТЧИКА / Глава 10 / Функция масштабирования вещественного в десятичное Сделать домашней страницей Добавить в избранное Написать писмо

НАЗАД СОДЕРЖАНИЕ ВПЕРЁД

Функция масштабирования вещественного в десятичное

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


+ 1.2345600000Е + 00,

Нам нужно каким-то образом преобразовать расстояние, которое в настоящее время выражает степень числа 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        ; конец программ
______________________________________________________________

НАЗАД СОДЕРЖАНИЕ ВПЕРЁД

Hosted by uCoz