Assembly 使用TASM计算浮点时出错

Assembly 使用TASM计算浮点时出错,assembly,floating-point,dos,tasm,x86-16,Assembly,Floating Point,Dos,Tasm,X86 16,我想用汇编程序做一个简单的操作。0.01+0.02使用手动输入和程序输入。在程序结束时,我预测了相同的正确结果,但我有一个不精确的结果 您可以手动更改变量tmph和tmpa,以确保它有问题(尝试1和2、0.1和0.2、0.01和0.02等) 结果总是很奇怪 我知道这是一个简单的操作,但为什么结果与预期略有不同?如何解决此问题 我的计划如下: model small .stack 100h .386 .data STRING_TAB DB ' ----->

我想用汇编程序做一个简单的操作。0.01+0.02使用手动输入和程序输入。在程序结束时,我预测了相同的正确结果,但我有一个不精确的结果

您可以手动更改变量
tmph
tmpa
,以确保它有问题(尝试1和2、0.1和0.2、0.01和0.02等) 结果总是很奇怪

我知道这是一个简单的操作,但为什么结果与预期略有不同?如何解决此问题

我的计划如下:

model small
.stack 100h
.386
.data
            STRING_TAB DB  '    ----->    $'

            tmph dd 0.01
            tmpa dd 0.02
.code

start:
mov ax, @data                                                           ;заносим область с данными
mov ds, ax                                                              ;в рабочую зону DS
finit
                                                                        ;fstp   st(1)          ; Удаляем st1
org 100h
mov  ax,2
int  10H                                                                ;установка видеорежима 80x25

call infloat ;enter 0.01
call infloat ;enter 0.02  
fadd
call outfloat ;compare

lea dx,STRING_TAB
mov ah,09h
int 21h
fld tmph
fld tmpa
fadd
call outfloat ;compare
                ;i want to see 0.3  -  0.3

mov ah, 4ch                                                             ;передаём в ah код прерываня для выхода из программы
int 21h                                                                 ;прерываение

infloat proc    near
    push    ax                                                      ;сохранение регистра ax
    push    dx                                                      ;регистра dx
    push    si                                                      ;регистра si
                                                                    ;Формируем  стэк, чтобы хранить десятку и ещё какую-нибудь цифру.
    push    bp                                                      ;регистра bp
    mov     bp, sp                                                  ;помещаем в bp указатель стека
    push    10                                                      ;заносим в стек  10
    push    0                                                       ;заносим в стек  0
    xor     si, si                                                  ; В SI хранится знак.

                                                                    ; Начнём накапливать число. Сначала это ноль.
    fldz
    mov     ah, 01h                                                 ;Вводим первый символ
    int     21h                                                     ;через первую функцию 21го прерывания
    cmp     al, '-'                                                 ;сравниваем введённое значение с символом "-"
    jne     short @if1                                              ;если "-" то запоминаем, если нет то проверяем следующие условия
    inc     si                                                      ;запомиинаем минус в регистре si
@if0:
    mov     ah, 01h                                                 ;Вводим  символ
    int     21h                                                     ;через первую функцию 21го прерывания


@if1:
    cmp     al, '.'                                                 ;Если введена точка, то
    je      short @if2                                              ;формируем дробную часть


    cmp     al, 39h                                                 ;проверяем
    ja      short @if5                                              ;что вводим числа,
    sub     al, 30h                                                 ;и в случае если вводятся не числа,
    jb      short @if5                                              ;то переходим по метке завершающей ввод
                                                                    ;сохраним её во временной ячейке и допишем
                                                                    ; к текущему результату справа,
    mov     [bp - 4], al                                            ;переместим введённое число в память
    fimul   word ptr [bp - 2]                                       ;домножим верх стека на 10
    fiadd   word ptr [bp - 4]                                       ;добавим к верху стека введённое число
    jmp     short @if0                                              ;повторяем
@if2:                                                                   ;метка вычисления дробной части
fld1                                                                   ;добавляю в верх стека единицу
@if3:
    mov     ah, 01h                                                 ;принимаем
    int     21h                                                     ;символ

    cmp     al, 39h                                                 ;проверяем
    ja      short @if4                                              ;что вводим числа,
    sub     al, 30h                                                 ;и в случае если вводятся не числа,
    jb      short @if4                                              ; то переходим по метке завершающей ввод

    mov     [bp - 4], al                                            ;иначе сохраняем её во временной ячейке,
    fidiv   word ptr [bp - 2]                                       ;получаем очередную отрицательную степень десятки,
    fld     st(0)                                                   ;записываем её в стек
    fimul   word ptr [bp - 4]                                       ;помножаем на введённую цифру, тем самым получая её на нужном месте
    faddp   st(2), st                                               ;и добавляем к  результату.
    jmp     short @if3                                              ;повторяем


@if4:
fstp    st(0)                                                           ;на вершине стэка получено введённое число.
@if5:
    mov     ah, 02h                                                 ;вывод на экран
    mov     dl, 0Dh                                                 ;перевод каретки
    int     21h
    test    si, si                                                  ;проверяем  наличие знака
    jz      short @if6                                              ;если флаг  не ноль
    fchs                                                            ;то меняем в стеке знак
@if6:   leave
    pop     si                                                      ;восстанавливаем регистр si
    pop     dx                                                      ;восстанавливаем регистр dx
    pop     ax                                                      ;восстанавливаем регистр ax
    ret
infloat endp

outfloat proc near
    push    ax                                                      ;сохраняем регистр ах
    push    cx                                                      ;регистр cx
    push    dx                                                      ;регистр dx
    push    bp                                                      ;регистр bp
    mov     bp, sp                                                  ;помещаем в bp указатель стека
    push    10                                                      ;заносим в стек  10
    push    0                                                       ;заносим в стек  0

    ftst                                                            ;Проверяем число на знак, и если оно отрицательное
    fstsw   ax                                                      ;сохраняем флаги
    sahf                                                            ;помещает значение регистра ah в младший байт флагового регистра.
    jnc   @of1                                                      ;проверяем отрицание

    mov     ah, 02h                                                 ;выводим
    mov     dl, '-'                                                 ;минус
    int     21h
    fchs                                                            ;берём модуль числа

@of1:
    fld1
    fld     st(1)
    fprem                                                           ;Остаток от деления в вершине стека
    fsub    st(2), st                                               ;вычитаем из исходного числа
    fxch    st(2)                                                   ;меняем местами
    xor     cx, cx                                                  ;обнулим cx для того, чтобы считать количество цифр до запятой
                                                                    ;Поделим целую часть на десять,
@of2:
    fidiv   word ptr [bp - 2]                                       ;поделим на 10 вершину
    fxch    st(1)                                                   ;поменяем местами вершину и 1й элемент
    fld     st(1)                                                   ;число  1  число  дробь

    fprem                                                           ;снова берём остаток от вершины

    fsub    st(2), st                                               ;и от последующего разряда оставляем только целую часть

    fimul   word ptr [bp - 2]                                       ;домножим этот остаток на 10
    fistp   word ptr [bp - 4]                                       ;сохраним цифру во временной ячейке вершины стека.те самую близкую к точке с левой стороны
                                                                    ;сейчас в стеке осталось в вершине 1 , 1-й целая часть без одного разряда стоящего ближе к точке, 2-й дробь
    inc     cx                                                      ;увеличим счётчик, чтобы знать сколько выводим цифр из стека.
    push    word ptr [bp - 4]                                       ;сохраняемся
    fxch    st(1)                                                   ;меняем местами вершину и первый элемент, чтобы заново пройти цикл

    ftst                                                            ;проверяемся на ноль
    fstsw   ax                                                      ;сохраняем флаги
    sahf                                                            ;смотреть выше
    jnz     short @of2                                              ;Так будем повторять, пока от целой части не останется ноль.

    mov     ah, 02h                                                 ;выведем цифру
@of3:                                                                   ;метка для вывода уже всех чисел до запятой из стека
    pop     dx                                                      ;Вытаскиваем очередную цифру, переводим её в символ и выводим.
    add     dl, 30h
    int     21h
    loop    @of3                                                    ;И так, пока не выведем все цифры работает флаг cx
                                                                    ;работа с дробной частью
    fstp    st(0)
    fxch    st(1)                                                   ;поменяем местами
    ftst                                                            ;проверим наличие дробной части
    fstsw   ax
    sahf
    ;jz      short @of5                                              ; если её нет то идём на выход

    mov     ah, 02h
    mov     dl, '.'                                                 ; Если она всё-таки ненулевая, выведем точку
    int     21h
    mov     cx,                                                    ;максимум 6цифр после запятой

@of4:
    fimul   word ptr [bp - 2]                                       ;Помножим дрообную часть на десять (разница в том, что мы умножаем на 10, а не делим)
    fxch    st(1)                                                   ;та же операция как и с целыми
    fld     st(1)                                                   ;ставим в верх домноженную на 10 дробь

    fprem                                                           ; отделим целую часть
    fsub    st(2), st                                               ; оставим от домноженной на 10дроби лишь дробную часть
    fxch    st(2)                                                   ;поменяем местами верх и второй элемент

    fistp   word ptr [bp - 4]                                       ; сохраним полученную цифру во временной ячейке, чтобы можно было потом с ней работать
    mov     ah, 02h                                                 ; и сразу выведем.
    mov     dl, [bp - 4]
    add     dl, 30h
    int     21h

    fxch    st(1)                                                   ;снова проверяем на наличие нуля в остатке
    ftst                                                            ;(спрашивается зачем делать два раза,
    fstsw   ax                                                      ; потому что при первоначальной проверке дробь может отсутствовать )
    sahf
    loopnz  @of4                                                    ;пока не выведем 6 цифр (регистр CX)

@of5:
    fstp    st(0)                                                   ;очищаем остатки стека
    fstp    st(0)
    leave
    pop     dx                                                      ;восстанавливаем все регистры
    pop     cx
    pop     ax
    ret

outfloat endp
end start
这是运行我的程序的结果:


[

浮点存储为1.x*2^y
因此,要存储一个值,它必须是1/2、1/4、1/8等的总和。
虽然0.75没有问题,但0.75=1/2+1/4=(二进制)1.1 x 2^(-1))
0,1和0,2都不能用这种方式表示


(这与您尝试用十进制表示1/3或1/7的问题相同)

CPU必须使用特定数量的二进制数字(位)表示
0.01
(1/100)和
0.02
(1/50)及其和(3/100)这不能精确地完成。类似于不能仅使用3个十进制数字精确地表示
1/12345
,或在任何有限位数中表示
1/3
。添加1和2应给出浮点中3的精确结果。添加其他数字(0.01+0.02,0.1+0.2)不会给出确切的答案,因为这些数字不能精确地表示为浮点数。