2016-03-14 1 views
0

J'essaie de faire une opération simple en utilisant l'assembleur. 0,01 + 0,02 en utilisant l'entrée manuelle et l'entrée de programme. À la fin du programme, je prédis le même résultat correct, mais j'ai un résultat inexact.Erreur dans le calcul du flottant en utilisant TASM

Vous pouvez modifier manuellement la variable tmph et tmpa pour vous assurer qu'il est un problème (essayez 1 et 2, 0,1 et 0,2, 0,01 et 0,02, etc.) le résultat est toujours étrange.

Je sais que c'est une opération simple, mais pourquoi le résultat est légèrement différent de ce qui est attendu? Comment puis-je résoudre ce problème?

Mon programme est comme suit:

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 

C'est le résultat de l'exécution de mon programme:

[enter image description here

+5

http: // stackoverflow. com/questions/588004/is-floating-point-math-broken –

+0

La CPU doit représenter '0,01' (1/100) et' 0,02' (1/50) et leur somme (3/100) en utilisant un nombre spécifique de chiffres binaires (bits) qui ne peuvent pas être faits exactement. Semblable à ne pas pouvoir représenter '1/12345' en utilisant seulement 3 chiffres décimaux exactement, ou' 1/3' en n'importe quel nombre fini de chiffres. – lurker

+0

Ajout de 1 et 2 devrait donner le résultat exact de 3 en virgule flottante. Ajouter les autres nombres (0.01 + 0.02, 0.1 + 0.2) ne donnera pas de réponses exactes car ces nombres ne peuvent pas être représentés exactement comme des nombres à virgule flottante. –

Répondre

0

flottants sont stockés sous forme 1.x * 2^y
donc pour stocker une valeur, il doit être une somme de 1/2, 1/4, 1/8 etc.
alors que 0,75 n'est pas un problème, 0,75 = 1/2 + 1/4 = (binaire) 1.1 x 2^(- 1))
ni 0,1 ni 0,2 peut être représenté de cette façon

(c'est le même problème que si vous essayez représentez 1/3 ou 1/7 en décimal)