2016-03-14 4 views
0

Я пытаюсь выполнить простую операцию с использованием ассемблера. 0,01 + 0,02 с использованием ручного ввода и ввода программ. В конце программы я предсказываю тот же правильный результат, но у меня есть неточный результат.Ошибка при вычислении поплавка с использованием TASM

Вы можете вручную изменить переменную 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 

Это результат работы моей программы:

[enter image description here

+5

HTTP: // StackOverflow. com/questions/588004/is-floating-point-math-broken –

+0

CPU должен представлять '0.01' (1/100) и' 0.02' (1/50) и их сумму (3/100), используя определенное количество двоичные цифры (биты), которые не могут быть выполнены точно. Подобно тому, что он не может представлять '1/12345', используя только 3 десятичные цифры, или' 1/3' в любом конечном количестве цифр. – lurker

+0

Добавление 1 и 2 должно дать точный результат 3 в плавающей запятой. Добавление других чисел (0,01 + 0,02, 0,1 + 0,2) не даст точных ответов, потому что эти цифры не могут быть представлены точно как числа с плавающей запятой. –

ответ

0

плавающей точки сохраняются в 1.x * 2^у
поэтому, чтобы сохранить значение, оно должно быть суммой 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 в десятичной системе)

Смежные вопросы