7.8.3. Изображение чисел с плавающей точкой

Следующая подпрограмма берет число из вершины стека и преобразует его в печатную строку символов. Фактически, подпрограмма извлекает число с вершины стека и посылает его на дисплей. Далее эта подпрограмма будет использована в двух примерах для вывода их результатов. Исходный текст программы показан на Фиг. 7.25.

Эта подпрограмма достаточно просто строит выводимую символьную строку. Если исходное число NAN, либо бесконечность, или другое специальное число сопроцессора 8087, результат будет показан неверно. Первая часть программы - удобное место для использования команды FXAM, которая определила бы тип числа в вершине стека. Но в данном примере эта команда не используется, так как предполагается, что исходное число имеет нужный тип.

Эта программа не приспособлена для оформления формата выводимого числа. Результат всегда содержит знак (либо пробел, либо знак "-") и целую часть, состоящую из одной цифры. После десятичной точки расположены восемь десятичных позиций, а затем буква E и три позиции цифр для степени 10. Результат работы этой программы не так хорош, как можно было желать, но он позволяет видеть результат работы программы в читабельной форме. Более красивое преобразование числа потребовало бы значительно больше команд, и лишь малая часть из них помогла бы пониманию работы сопроцессора 8087.

Программа преобразования работает следующим образом. Сначала она определяет порядок числа. Например, число 1234 имеет порядок 3; это означает, что оно находится между значениями 10^3 и 10^4. Найдя правильный порядок числа, программа сохраняет его значение (показатель степени результата) и делит исходное число на 10 в этой степени. Теперь число находится в интервале от 1 до 10. Затем программа умножает число на 10^8. Запись этого числа в десятичной форме дает девять десятичных цифр; старшая цифра - целая часть, младшие восемь цифр - дробные разряды.


Microsoft (R) Macro Assembler Version 5.00                  1/1/80 04:03:56
Фиг. 7.25 Преобразование плавающего формата в текстовый           Page  1-1
PAGE  ,132
                         TITLE    Фиг. 7.25 Преобразование плавающего формата в текстовый
0000                     CODE     SEGMENT PUBLIC

                                  ASSUME    CS:CODE,DS:CODE,ES:CODE
                         EXTRN    TEN_TO_X:NEAR
0000    ????             OLD_CW   DW       ?
0002    ????             NEW_CW   DW       ?
0004    ????             EXPONENT DW       ?
0006    ???????????????????  BCD_RESULT    DT        ?
0010    ???????????????????  BCD_EXPONENT  DT        ?
001A    00E1F505         TEN8     DD       100000000
001E    20 20 20 20 20 20 20   PRINT_STRING      DB  '           E      ',10,13,'$'
        20 20 45 20 20 20 20
        0A 0D 24

                         PUBLIC   FLOAT_ASCII
                         ;--------------------------------------------
                         ; Эта программа извлекает из вершины стека
                         ; сопроцессора 8087 число и выводит его на
                         ; экран в плавающем формате.
                         ; Параметры: число в ST(0)
                         ; Результат: изображение числа на экране;
                         ; число извлечено из стека сопроцессора 8087
                         ;--------------------------------------------
002F                     FLOAT_ASCII        PROC      NEAR
                                                      ;-----ST(0)-----;-----ST(1)-----;--ST(2)--
                                                      ; X             ; ?             ; ?
002F    9B D9 C0                  FLD     ST(0)       ; X             ; X             ; ?
0032    9B D9 E1                  FABS                ; |X|           ; X             ; ?
0035    9B D9 E8                  FLD1                ; 1             ; |X|           ; X
0038    9B D9 C9                  FXCH   ST(1)        ; |X|           ; 1             ; X
003B    9B D9 F1                  FYL2X               ; LOG2(X)       ; X             ; ?
003E    9B D9 E9                  FLDL2T              ; LOG2(10)      ; LOG2(X)       ; X
0041    9B DE F1                  FDIVRP ST(1),ST(0)  ; E=LOGX/LOG10  ; X             ; ?
0044    D9 3E 0000 R              FNSTCW OLD_CW       ;               ;               ;
0048    9B                        FWAIT               ;               ;               ;
0049    A1 0000 R                 MOV    AX,OLD_CW    ;               ;               ;
004C    25 F3FF                   AND    AX,NOT 0C00H ;               ;               ;
004F    0D 0400                   OR     AX,0400H     ;               ;               ;
0052    A3 0002 R                 MOV    NEW_CW,AX    ;               ;               ;
0055    9B D9 2E 0002 R           FLDCW  NEW_CW       ;               ;               ;
005A    9B D9 FC                  FRNDINT             ; I = INT(E)    ; X             ; ?
005D    9B D9 2E 0000 R           FLDCW  OLD_CW       ;               ;               ;
0062    9B DF 16 0004 R           FIST   EXPONENT     ; I             ; X             ; ?
0067    9B D9 E0                  FCHS                ; -I            ; X             ; ?
006A    E8 0000 E                 CALL   TEN_TO_X     ; 10 ** (-I)    ; X             ; ?
006D    9B DE C9                  FMULP  ST(1),ST(0)  ; X/10**I       ; ?             ; ?
0070    9B DA 0E 001A R           FIMUL  TEN8         ; Мантисса      ; ?             ; ?
0075    9B DF 36 0006 R           FBSTP  BCD_RESULT   ; ?             ; ?             ; ?
007A    9B DF 06 0004 R           FILD   EXPONENT     ; I             ; ?             ; ?
007F    9B DF 36 0010 R           FBSTP  BCD_EXPONENT ; ?             ; ?             ; ?
                         ;-----   Вывод на экран значений,запомненных как упакованные
                         ;        целые двоично-десятичные числа
0084    FC                        CLD
0085    8D 3E 001E R              LEA    DI,PRINT_STRING  ; Указатель на выводимую строку
0089    A0 000F R                 MOV    AL,BYTE PTR BCD_RESULT+9
008C    E8 00C3 R                 CALL   PRINT_SIGN       ; Печать знака
008F    A0 000A R                 MOV    AL,BYTE PTR BCD_RESULT+4
0092    E8 00DF R                 CALL   PRINT_NYBBLE     ; Печать первой цифры
0095    B0 2E                     MOV    AL,'.'           ; Десятичная точка
0097    AA                        STOSB
0098    8D 1E 0009 R              LEA    BX,BCD_RESULT+3
009C    B9 0004                   MOV    CX,4             ; Печать 8 байт (16 цифр)
009F                         DO_BYTE:                     ; после десятичной точки
009F    E8 00CD R                 CALL   PRINT_BYTE
00A2    E2 FB                     LOOP   DO_BYTE
00A4    B0 45                     MOV    AL,'E'           ; Символ экспоненты
00A6    AA                        STOSB
00A7    A0 0019 R                 MOV    AL,BYTE PTR BCD_EXPONENT+9
00AA    E8 00C3 R                 CALL   PRINT_SIGN       ; Печать знака экспоненты
00AD    A0 0011 R                 MOV    AL,BYTE PTR BCD_EXPONENT+1
00B0    E8 00DF R                 CALL   PRINT_NYBBLE     ; Первая цифра экспоненты
00B3    8D 1E 0010 R              LEA    BX,BCD_EXPONENT
00B7    E8 00CD R                 CALL   PRINT_BYTE       ; Оставшиеся цифры
00BA    8D 16 001E R              LEA    DX,PRINT_STRING
00BE    B4 09                     MOV    AH,9H
00C0    CD 21                     INT    21H              ; Вывод всей строки на экран
00C2    C3                        RET
00C3                    FLOAT_ASCII      ENDP

                        ;-----   Эта подпрограмма выводит ' ' или '+'
00C3                    PRINT_SIGN       PROC      NEAR
00C3    3C 00                     CMP    AL,0             ; Проверка на знак
00C5    B0 20                     MOV    AL,' '           ; Занесение положительного знака
00C7    74 02                     JZ     POSITIVE
00C9    B0 2D                     MOV    AL,'-'           ; Занесение минуса
00CB                     POSITIVE:
00CB    AA                        STOSB                   ; Сохранение в выводимой строке
00CC    C3                        RET
00CD                     PRINT_SIGN      ENDP

                         ;-----   Эта программа печатает две десятичные цифры,
                         ;        находящиеся в памяти по адресу [BX]
00CD                     PRINT_BYTE      PROC      NEAR
00CD    8A 07                     MOV    AL,[BX]          ; Выборка байта
00CF    51                        PUSH   CX
00D0    B1 04                     MOV    CL,4
00D2    D2 E8                     SHR    AL,CL            ; Сдвиг старшей цифры
00D4    59                        POP    CX
00D5    E8 00DF R                 CALL   PRINT_NYBBLE     ; Печать старшей цифры
00D8    8A 07                     MOV    AL,[BX]          ; Выборка младшей цифры
00DA    E8 00DF R                 CALL   PRINT_NYBBLE     ; Печать младшей цифры
00DD    4B                        DEC    BX               ; Переход на следующую пару цифр
00DE    C3                        RET
00DF                     PRINT_BYTE      ENDP

                         ;-----   Печать одной десятичной цифры из регистра AL
00DF                     PRINT_NYBBLE    PROC      NEAR
00DF    24 0F                     AND    AL,0FH           ; Выделение младшей цифры
00E1    04 30                     ADD    AL,'0'           ; Преобразование в символ
00E3    AA                        STOSB                   ; Сохранение в выводимой строке
00E4    C3                        RET
00E5                     PRINT_NYBBLE      ENDP

00E5                     CODE     ENDS
                         END

                         Фиг. 7.25 Преобразование плавающего формата в текстовый 

Первая часть программы определяет правильный порядок исходного числа. В программе логарифм числа по основанию 10 находится с помощью формулы


Log10(X) = Log2(X)/Log2(10)

Затем округляется порядок в направлении минус бесконечности, опять используя управление округлением. В предыдущем примере, вычисляя 10X, мы пользовались умножением для переноса исходного числа в нужный диапазон. Теперь мы используем константу TENB (которая содержит целое число 108) для того, чтобы вернуть число в нужный диапазон. Наконец, команда FBSTP дважды преобразует числа в десятичное представление; сначала она дает нам девять цифр мантиссы числа, а затем - три цифры порядка. Остальная часть программы выполняет символьную обработку, необходимую для преобразования десятичного представления в строки символов. Программа определяет и показывает знаки числа и порядка. Она распаковывает десятичные байты и преобразует их в символы; подпрограмма PRINT_BYTE делает распаковку, а подпрограмма PRINT_NYBBLE выполняте преобразование в символы. Заметим, что в этом случае не нужна команда XLAT, так как все цифры имеют значения между 0 и 9. (Но если исходное число - одно из неопределенных чисел, символьная строка будет содержать некоторые непонятные символы).

Эта программа верно печатает любое число, лежащее в диапазоне длинных действительных чисел. Любое число, выходящее за пределы возможностей этого представления (например 101234) имеет поле порядка, сокращенное до трех цифр. Конечно, вы можете изменить программу так, чтобы она обрабатывала четыре цифры поля порядка, если вы этого желаете. Но существует все же число, которое программой обрабатывается верно, но вы, возможно, пожелаетет изменить его изображение. Если исходное число 0, результат печатается в виде 0.00000000E-9^32. Так происходит потому, что поле порядка имеет смещение; процессор 8087 представляет число 0 с минимально возможным порядком (-4932) и с нулевой мантиссой. Когда программа преобразует число в код ASCII, она верно печатает мантиссу и порядок (за исключением того, что ей приходится усекать порядок до трех цифр). Если вы захотите обрабатывать такой порядок отдельно, то измените программу, вставив в нее проверку на нуль (чаще всего, с помощью команды FTST) в самом начале, рассматривая это, как специальный случай.