Вещественное в строку
(с) Никита Культин, 2014.
Ниже приведена функция _si2st, которая преобразует single значение в строку. Идея алгоритма заключается в нормализации исходного числа (делает это функция _si2nsi) и последующем умножении нормализованного числа на 10 для получения очередной значащей цифры. Число и требуемый формат отображения (количество цифр целой и дробной частей), передаются функции через стек. Если количество цифр дробной части задано равным нулю, то формируется экспоненциальное представление. Если число такое, что отобразить его в формате с точкой нельзя, то тоже формируется экспоненциальное представление.
// ------------------------------------
// Преобразует single в СТРОКУ!
(с) Никита Культин, 2014.
_si2st:
// _mm - общее кол-во позиций
// _nn - кол-во цифр дробной части
// _pp - позиция точки
// _kk - кол-во пробелов перед числом
// _ff - флаг формата
// _fs - флаг знака порядка
// float_10 - DWORD, в котором
// находится 0x41200000 - single значение 10
// _buf - строковое представление числа
mov ebx, [esp]+4
mov eax, [ebx]
mov r1, eax
mov eax, [esp]+12 // кол-во цифр др. части
mov _nn, eax
mov eax, [esp]+8 // кол-во позиций
test eax, eax
jz a00
dec eax
a00: mov _mm, eax
mov _kk, eax
xor eax, eax
mov _fs, eax // флаг знака порядка
mov _ff, eax // флаг
// чтобы узнать порядок числа, нормализуем его
push offset r1 // исходное число
push offset r2 // нормализованное число
push offset p // порядок нормализованного числа
mov ebx, offset si2nsi
call ebx
add esp, 12
a0: cmp _mm, 0
je a2 // вывод в Е формате
//mov eax, _mm
//test eax, eax
//jz a2
cmp _nn, 0 // вывод в m:n формате
je a2
//mov eax, _nn
//test eax, eax
//jnz a1
// вывод в m формате
//mov eax, _mm
//cmp eax, 9
//jb a2
//sub eax, 9
//mov _kk, eax // _kk = _mm -9
//jmp a2
a1: // m:n формат
// проверить знак порядка
mov ecx, p
cmp ecx, 0
jge a9
// p<0
inc _fs
neg ecx
cmp ecx, _nn
jg a2 // выводим в e-формате
inc _ff
xor eax, eax
mov _pp,eax
mov eax, _nn
add eax, 2
mov _mm,eax
// число с отрицательным порядком приведем к числу
// с порядком 0. Будем делить на 10
a8:test ecx, ecx
jz a3
fld r2
fld float_10
fdiv
fstp r2
dec ecx
jmp a8
a9: // p>= 0
// вывести можно?
// т.е. (m > p+2+n) AND (n+p < 7), где 7 - кол-во значащих цифр для типа single
mov eax, p
add eax, _nn
cmp eax, 6
jg a2 // n+p > 7
add eax, 2
// выполняется условие m > p+2+n ?
// cmp eax, _mm
// ja a2
// вывод в m:n формате
mov _mm,eax // общее кол-во цифр
//xor eax, eax
cmp _fs, 0 //
jnz a4
mov eax, p
a4:mov _pp, eax
inc _ff
jmp a3
a2: // значение такое, что в формате m:n отобразить нельзя
mov eax, 8
mov _mm, eax
xor eax,eax
mov _pp,eax
mov _ff,eax
//
a3: // вывод
//
lea edi, _buf
// вычистить кол-во ведущих пробелов
mov ecx, _kk
sub ecx, _mm
cmp _ff, 0 // e-формат?
jnz a33
sub ecx, 4 // кол-во позиций вывода порядка
a33:
mov eax, 32 // пробел
a31: cmp ecx, 0
jle a32
stosb
dec ecx
jmp a31
// При выделении целой части числа командой fist округление
// по умолчанию выполняется "с избытком".
// Нам необходимо отбросить дробную часть. Поэтому
// установим режим округления "отбросить дробную часть".
// Чтобы это сделать, надо установить 10 и 11 биты слова управления.
a32: fstcw cwr // читать слово управления сопроцессора
mov ax, cwr
or ax, $0c00 // установить 10 и 11 биты
mov cwr, ax
fldcw cwr // записать слово управления
fld r2 // загрузить число в сопроцессор
// Теперь не надо!
// // проверка на НОЛЬ!
// FLDZ
// FCOMp
// fstsw ax // результат проверки сохранить в ax
// test ax, $4000 // значение равно нулю?
// jnz m0 // да!
// Число положительное или отрицательное?
fxam // проверить число
fstsw ax // результат проверки передать в ax
and ax, $0200 // выделить флаг c1.
// с1 = 1 - число отрицательное; c1 = 0 - число положительное
cmp ah, 2 // проверить c1
jne m5
mov eax, $2d // в знаковой позиции минус
fabs
jmp m6
m5: mov eax, $20
m6: stosb // знак: пробел или минус
xor ecx, ecx
m0: fist n1 // n1 - целая часть
mov eax, n1
// в результате округления возможно, что
// n1 = 10. Что НЕПРАВИЛЬНО!
cmp eax, 10
jl m06
// Корректируем
mov eax, 1
inc p
m06:add eax, 48
stosb
// ставим точку?
cmp ecx, _pp
jne m01
mov eax, 46 // точка
inc ecx
stosb
m01:inc ecx
cmp ecx, _mm
je m02
fild n1
fsub
fld float_10
fmul
jmp m0
m02: mov eax, _ff
cmp eax, 0
jnz m05
mov eax, 69 // символ E
stosb
// знак порядка
mov ecx, 43 // "+"
mov eax, p
cmp eax, 0
jge m03
mov ecx, $2d // "-"
neg p
m03: mov eax, ecx
stosb
// цифры порядка
mov eax, p
// будем печатать две цифры
// cmp p, 10
// jb m04
// порядок больше 10
fild p
fld float_10
fdiv
fistp n1
mov eax, n1
add eax, 48
stosb
fild p
fld float_10
fild n1
fmul
fsub
fistp p
m04: mov eax, p
add eax, 48
stosb
m05: xor eax, eax
stosb
ret 12
//////////////////////////////////////////////////////////
// Нормализует single.
// (с) Никита Культин, 2014.
// Результат: нормализованное single и порядок
_si2nsi:
mov ebx, [esp]+12
mov eax, [ebx]
mov _r, eax
FINIT
fld _r
// число на вершине стека сопроцессора
mov p, 0 // порядок
// проверить значение на вершине стека:
// - равно нулю?
// - отрицательное?
FLDZ
FCOMp
fstsw ax // результат проверки сохранить в ax
test ax, $4000 // значение равно нулю?
jnz k5 // да!
test ax, $100 // значение отрицательное?
jnz k0 // нет
// отрицательное
FCHS // изменить знак
k0:
FLD1 // множитель, учитывающий знак числа
test ax, $100 // значение отрицательное?
jnz k01 // нет
FCHS
k01: // на вершине стека число, в st(1) 1 или -1
FXCH st(1)
// теперь на вершине стека число
// больше 10?
k1: FLD float_10
FCOM // сравнить значения
fstsw ax // результат проверки передать в ax
test ax, $4500
jz k2 // меньше 10 - да! Проверить: больше 1?
// мантисса больше 10
FDIV
inc p
jmp k1
k2: FCOMP // выкинуть 10 с вершины стека (другой способ придумать не могу)
k3: // меньше единицы?
FLD1
FCOMP // сравнить значения
fstsw ax // результат проверки передать в ax
test ax, $4100
jnz k4 // меньше или равен 1 !
fmul float_10
dec p
jmp k3
k4: // на вершине стека нормализованное число, в st(1) 1 или -1
fmulp
k5: fstp _r
// значения возвращаем через стек
mov eax, _r
mov ebx, [esp]+8
mov [ebx], eax
mov eax, p
mov ebx, [esp]+4
mov [ebx], eax
ret
/////////////////////////////
// Преобразовать single в строку
push 9; // кол-во цифр дробной части
push 2; // кол-во позиций для вывода числа
push offset r // число
mov ebx, offset si2st
call ebx