Очень вещественная арифметика
Попробуем-таки добавить на наш арифмометр кнопку с
символом "точка". Графику трогать не будем - остановимся только на арифметике, например
- сложение:
...
add_32 proc
finit
fld float1 ;float1 dd ?
fld float ;float dd ?
fadd
fstp res ;res dq ?
ret
add_32 endp
...
Преобразование строка - число
Сопроцессор работает и с целыми, и с дробными (вещественными) числами. Преобразование ASCII->
двоичный код, для целых чисел, мы уже рассматривали. Для дробных чисел - оно несколько
сложнее. Но - не на много.
Когда числа задаются в
.data, то
процессор сам преобразует их в "удобоваримый" вид. В нашем случае - мы не знаем, какими
будут эти числа? Поэтому, нам придётся учитывать возможность наличия в числе дробной части.
Вспомним принцип преобразования целого числа:
(((0*10)+1)*10+2)*10+3=123
Если идти по тропинке Горнера, то дробную часть можно преобразовать так:
(((3*0.1+2)*0.1+1)*0.1+0=0.123
После чего - сложить оба результата. Выглядит заманчиво, но...сопроцессор оперирует
своим форматом чисел. И получается этот формат при использовании
команд
сопроцессора. Поэтому
процедуру преобразования нам придётся несколько
видоизменить.
Поскольку с преобразованием целого (целой части) числа мы уже знакомы,
то остановимся поподробнее на преобразовании дробной части:
принцип формирования дробной части float:
число 0.123(.123)
цикл: 1-й 2-й 3-й
что делаем: 1*0.1+0 2*0.01+0.1 3*0.001+0.12
итог: 0.1 0.12 0.123
рис.9.1
Если число меньше единицы (рис.9.1), то в вершине стека у нас будет
ноль (st=
0),
котрый мы прибавляем в первом цикле. Если-же число больше единицы - в вершине стека будет целая
часть числа.
А выглядеть это будет примерно так:
pushad ;сохраняем значения регистров
mov dot,0 ;инициируем dot
lea esi,stroka ;строка символов
lea edi,float ;вещественный результат
finit ;обрабатываем необработанные исключения
fldz ;st=0
cld ;устанавливаем направление переноса - от начала к концу цепочки
cycl:
xor eax,eax ;чистим регистр
lodsb ;копируем байт/символ из DS:(E)SI в AL
cmp al,0 ;если нет символа
je fini ;на выход
mov dl,al ;только для '.'
and al,0Fh ;преобразуем
movzx eax,al ;расширяем
mov [edi],eax ;и сохраняем
cmp dot,0 ;была точка?
jg c_dec ;если да (dot > 0)
cmp dl,'.' ;это точка?
je c_dot ;если да
c_int: ;работаем с целой частью числа
fimul ten ;st=0*10...
fiadd dword ptr [edi] ;st=0*10+float, накапливаем дв.код целой части
jmp cycl ;повторяем с целой частью
c_dot: ;встретили точку
mov dot,1h ;фиксируем наличие точки и готовим множитель для дробной части
fld1 ;st=1 st(1)=float<-в начале=0
fidiv ten ;st=1:10=0.1 st(1)=float
fstp aten ;st=float aten=0.1
jmp cycl ;к след.символу
c_dec:
fld aten ;st=0.1 st(1)<-в начале float=0
fimul dword ptr [edi] ;st=0.1*1 st(1)=float
faddp st(1),st ;st=0.1*1+0...накапливаем дв.код дробной части
fld aten ;st=0.1 st(1)=float
fidiv ten ;st=0.1/10 st(1)=float
fstp aten ;st=float aten=0.01...
jmp cycl ;повторяем с дробной частью
fini:
fstp dword ptr [edi] ;сохраняем двоичный код во float
popad
рис.9.2
Как видно из кода (рис.9.2) - ничего нового мы не придумали, просто подключили сопроцессор, а
точнее, его команды.
Преобразование число - строка
Данное преобразование - вещь довольно мозголомная. После долгих
поисков в сети я остановился на наиболее, на мой взгляд, доходчивом варианте. Его автор
И.В.Лысков. Мы переделаем этот вариант под win32:
pushad
lea edi,stroka
finit ;инициируем FPU
fld res ;st=res (в примере res=73.25)
ftst ;st=0.0?
fstsw ax ;копируем слово состояния, для основного процессора
sahf ;запись АН в EFLAGS,копирует биты: С3->ZF, С2->PF, а С0->СF
jnz m1 ;если res !=0 -> m1
mov ax,'0' ;добавляем на дисплей 0 -> результат арифметической операции = 0
stosw ;перенос содержимого AL(AX,EAX)в адрес памяти ES:(E)DI
jmp m6 ;и - выходим
m1:
jnc m2 ;если число '+', нет переноса
mov al,'-' ;если есть перенос - число '-'
stosb
fchs ;изменяем знак числа
; st st(1) st(2) st(3) ...
m2: ;73,25 - условное число, для наглядности
fld1 ;1 73,25
fld st(1) ;73,25 1 73,25
fprem ; 0,25 1 73,25
fsub st(2),st ; 0,25 1 73
fxch st(2) ;73 1 0,25
xor ecx,ecx ;ECX=0, готовим счётчик
m3:
fidiv ten ; 7.3 1 0.25
fxch st(1) ; 1 7.3 0.25
fld st(1) ; 7.3 1 7.3 0.25
fprem ; 0.3 1 7.3 0.25
fsub st(2),st ; 0.3 1 7 0.25
fimul ten ; 3 1 7 0.25
fistp dig ; 1 7 0.25
inc ecx ;уменьшаем счётчик
push dig ;сохраняем цифру для преобразования (m4)
fxch st(1) ; 7 1 0.25
ftst ;st=0.0?
fstsw ax
sahf
jnz m3 ;если цифра не 0 - повторяем
m4:
pop eax ;выталкиваем/копируем цифру в ЕАХ->AL
add al,30h ;и преобразуем в символ
stosb ;добавляем в stroka
loop m4 ; 0 1 0.25
;дробная часть: если есть
fstp st ; 1 0.25
fxch st(1) ; 0.25 1
ftst
fstsw ax
sahf
jz m6 ;если дробная часть = 0
mov al,"." ;добавляем точку в stroka
stosb
mov ecx,six ;заряжаем счётчик - 6 знаков после "."
m5:
fimul ten ; 2.5 1
fxch st(1) ; 1 2.5
fld st(1) ; 2.5 1 2.5
fprem ; 0.5 1 2.5
fsub st(2),st ; 0.5 1 2
fxch st(2) ; 2 1 0.5
fistp dig ; 1 0.5
mov eax,dig ;EAX = dig
add al,30h ;преобразуем цифру
stosb ;и добавляем в stroka
fxch st(1) ; 0.5 1
ftst ;если остаток не 0
fstsw ax
sahf ;повторяем по счётчику
loopnz m5 ; 0 1
m6:
fstp st ; 1
fstp st ; ... почистились
mov byte ptr [edi],0 ;закрываем stroka
fin:
popad
рис.10
Если внимательно присмотрется к коду (рис.10), то можно заметить, что арифмометр работает только с положительными числами. Что мы
делаем:
- берётся итог арифметической операции
- из него выделяется
целая часть
m2 - эта целая часть в цикле
m3 разбивается на отдельные цифры, которые сохраняются в памяти
- затем, в цикле
m4, эти цифры преобразуются в ASCII-символы и добавляются в буфер
stroka - после этого проверяется
наличие дробной части
m4 и добавляется/не добавляется точка
- если дробная часть существует, то в цикле
m5, она разбивается
на отдельные цифры, максимальное количество (точность вычисления) которых мы задаём
- в этом-же цикле
m5 цифры
дробной части преобразуются и добавляются в буфер
strokaЗаканчивая преобразование мы добавляем в буфер
0.
P.S.
Для желающих разобратся в представленном коде не спеша и без сети, можно скачать
архив с файлами. Компоновка архивной программы несколько отличается от стандартного вида, но никто
не запрещает переделать её по своему.
вперёд...
...назад