我试图使用汇编程序进行简单的操作。使用手动输入和程序输入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
这是运行我的程序的结果:
[
答案 0 :(得分:0)
浮点存储为1.x * 2 ^ y
所以要存储一个值,它必须是1 / 2,1 / 4,1 / 8等的总和
0.75 = 1/2 + 1/4 =(二进制)1.1 x 2 ^( - 1))
0,1或0都不能用这种方式表示
(这就像你尝试代表十进制的1/3或1/7一样的问题)