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:
[
http: // stackoverflow. com/questions/588004/is-floating-point-math-broken –
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
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. –