![Blog. Just Blog](/images/logo.png)
Преобразование вещественного числа в строку на Ассемблере
Ранее я выкладывал статью о том, как на Ассемблере перевести строку в вещественное число, теперь настало время произвести обратную операцию, то есть перевести вещественное число в строку. В отличие от целых чисел, где достаточно нескольких строк кода, с преобразованием вещественных чисел пришлось повозиться. В результате появилась следующая функция. За основу взята аналогичная функция из пакета MASM, но со значительными доработками.Code (Assembler) : Убрать нумерацию
- ;------------------------------------------------------------
- ; Функция перевода вещественного числа в строку
- ;------------------------------------------------------------
- ; Портирование с MASM, доработка и оптимизация - ManHunter
- ; Параметры:
- ; lpFloat - указатель на вещественное число TBYTE
- ; lpResult - указатель на строку-приемник результата
- ;------------------------------------------------------------
- proc FloatToString lpFloat:DWORD, lpResult:DWORD
- ; Локальные переменные
- local digits_count:DWORD
- local tmp:DWORD
- local old_cw:WORD
- local new_cw:WORD
- local saved_float:TBYTE
- local tmp1 rb 12h
- local tmp2 rb 12h
- ; Сохранить все регистры
- pusha
- ; Указатель на строку-приемник
- mov edi,[lpResult]
- ; Это ноль?
- mov esi,[lpFloat]
- cmp dword [esi],0
- jne loc_not_zero
- cmp dword [esi+4],0
- jne loc_not_zero
- cmp word [esi+8],0x8000
- je loc_minus_zero
- cmp word [esi+8],0
- jne loc_not_zero
- ; Записать в строку ноль
- mov al,'0'
- stosb
- jmp loc_ret
- loc_minus_zero:
- ; Записать в строку минус ноль
- mov ax,'-0'
- stosw
- jmp loc_ret
- loc_not_zero:
- ; Denormalized?
- mov ax,word [esi+8]
- and ax,7F80h
- or ax,ax
- jnz loc_not_denorm
- cmp dword [esi],0
- jne loc_denorm
- cmp dword [esi+4],0
- je loc_not_denorm
- loc_denorm:
- mov esi,szDen
- movsd
- movsd
- movsd
- jmp loc_ret
- loc_not_denorm:
- ; Infinity или NaN?
- mov ax,word [esi+8]
- and ax,7F80h
- cmp ax,7F80h
- jne loc_not_inf_nan
- ; Это Infinity?
- cmp dword [esi],0
- jne loc_nan
- cmp dword [esi+4],80000000h
- jne loc_nan
- loc_infinity:
- mov esi,szInf
- movsd
- movsb
- loc_plus_minus:
- movsd
- mov esi,[lpFloat]
- test byte [esi+9],80h
- jz loc_ret
- mov esi,[lpResult]
- mov byte[esi],'-'
- jmp loc_ret
- loc_nan:
- mov esi,szNan
- jmp loc_plus_minus
- loc_not_inf_nan:
- ; Скопировать число в локальную переменную
- push edi
- mov esi,[lpFloat]
- lea edi,[saved_float]
- movsd
- movsd
- movsw
- pop edi
- ; Число отрицательное?
- cmp dword [saved_float+6],0
- jge loc_not_signed
- ; Привести число к абсолютному значению
- and byte [saved_float+9],7Fh
- ; Записать в строку минус
- mov al,'-'
- stosb
- loc_not_signed:
- ; Проверить число на наличие дробной части и
- ; подсчитать количество цифр в нем
- fclex
- ; Сохранить управляющее слово
- fstcw [old_cw]
- ; Установить управляющее слово
- mov [new_cw],0000001001111111b
- fldcw [new_cw]
- lea esi,[saved_float]
- fld tbyte [esi]
- fld st
- ; Выделить мантиссу и порядок
- fxtract
- fstp st
- fldlg2
- ; Получить количество цифр в числе
- fmulp st1,st
- fistp [digits_count]
- ; Если цифр больше 16, то число отображается в
- ; нормализованном виде с мантиссой и экспонентой
- cmp [digits_count],10h
- jnb loc_not_integer
- ; У числа есть дробная часть?
- fld st
- frndint
- fcomp st1
- fstsw ax
- test ah,01000000b
- ; Да, отображать число с дробной частью
- jz loc_not_integer
- ; Целое число без дробной части и экспоненты
- lea eax,[tmp1]
- fbstp [eax]
- ; Перевести BCD-число в строку
- push edi
- lea esi,[tmp1+8]
- lea edi,[tmp2]
- mov ecx, 9
- @@:
- std
- xor eax,eax
- lodsb
- cld
- rol ax,12
- rol ah,4
- add ax,'00'
- stosw
- loop @b
- pop edi
- ; Пропустить лидирующий ноль
- mov eax,11h
- mov ecx,[digits_count]
- sub eax,ecx
- inc ecx
- lea esi,[tmp2+eax]
- cmp byte [esi],'0'
- jne @f
- inc esi
- dec ecx
- @@:
- ; Перенести полученное число из временного буфера
- rep movsb
- jmp loc_clear_stack
- loc_not_integer:
- mov eax,10h
- sub eax,[digits_count]
- ; Преобразовать число в целое до 16 разрядов
- mov ecx,eax
- cmp eax,0
- jge @f
- neg eax
- @@:
- ; Для чисел больше 0 корректировка округления в сторону 0
- mov [new_cw],0000101001111111b
- cmp ecx,0
- jge @f
- mov [new_cw],0000011001111111b
- @@:
- ; Установить управляющее слово
- fldcw [new_cw]
- ; Возвести 10 в степень количества цифр
- mov [tmp],eax
- fild [tmp]
- fld [float2]
- fyl2x
- fld1
- fld st1
- fprem
- f2xm1
- faddp
- fscale
- ; Почистить стек
- fxch st1
- fstp st
- ; Если число меньше 0, то умножить, иначе разделить
- cmp ecx,0
- jge @f
- fdivp st1,st
- jmp loc_rounded
- @@:
- fmulp st1,st
- loc_rounded:
- ; Полученное значение меньше 1.0e16 ?
- fcom [float1]
- fstsw ax
- test ah,1
- jz @f
- fmul [float2]
- dec [digits_count]
- @@:
- ; Целое число без дробной части и экспоненты
- lea eax,[tmp1]
- fbstp [eax]
- ; Перевести BCD-число в строку
- push edi
- lea esi,[tmp1+8]
- lea edi,[tmp2]
- mov ecx, 9
- @@:
- std
- xor eax,eax
- lodsb
- cld
- rol ax,12
- rol ah,4
- add ax,'00'
- stosw
- loop @b
- pop edi
- ; Числу требуется мантисса и экспонента?
- lea esi,[tmp2+1]
- mov ecx,[digits_count]
- cmp ecx,-0Fh
- jl loc_mantiss_and_exponent
- cmp ecx,10h
- jg loc_mantiss_and_exponent
- ; Заполнить дробную часть числа
- inc ecx
- cmp ecx,0
- jg @f
- mov ax,'0.'
- stosw
- neg ecx
- mov al,'0'
- rep stosb
- mov ecx,10h
- jmp loc_fraction_filled
- @@:
- rep movsb
- mov al,'.'
- stosb
- mov ecx,10h
- sub ecx,[digits_count]
- loc_fraction_filled:
- rep movsb
- jmp @f
- loc_clear_fraction:
- ; Удалить завершающие нули дробной части
- dec edi
- @@:
- cmp byte [edi-1],'0'
- jz loc_clear_fraction
- cmp byte [edi-1],'.'
- jnz @f
- dec edi
- @@:
- jmp loc_clear_stack
- loc_mantiss_and_exponent:
- ; Дробная часть мантиссы
- movsb
- mov al,'.'
- stosb
- movsd
- movsd
- movsw
- ; Удалить завершающие нули дробной части
- @@:
- cmp byte [edi-1],'0'
- jne @f
- cmp byte [edi-2],'.'
- je @f
- dec edi
- jmp @b
- @@:
- ; Символ и знак экспоненты
- mov al,'e'
- stosb
- mov al,'+'
- mov ebx,[digits_count]
- cmp ebx, 0
- jge @f
- mov al,'-'
- neg ebx
- @@:
- stosb
- ; Значение экспоненты
- mov eax,ebx
- mov ecx,10
- mov ebx,4
- @@:
- dec ebx
- xor edx,edx
- div ecx
- add dl,'0'
- mov [tmp1+ebx],dl
- or ebx,ebx
- jnz @b
- ; Пропустить лидирующие нули экспоненты
- mov ecx,4
- lea esi,[tmp1]
- @@:
- lodsb
- cmp al,'0'
- jne @f
- dec ecx
- jmp @b
- @@:
- dec esi
- rep movsb
- loc_clear_stack:
- ; Восстановить управляющее слово
- fldcw [old_cw]
- loc_ret:
- ; Окончание строки
- mov al,0
- stosb
- ; Восстановить все регистры
- popa
- ret
- float1 dq 1.0e16
- float2 dq 10.0
- szInf db '+Infinity'
- szNan db '+NaN'
- szDen db 'Denormalized'
- endp
Пример вызова функции перевода вещественного числа в строку. Обратите внимание, что исходное вещественное число в данных описано в ненормализованном виде.
Code (Assembler) : Убрать нумерацию
- ; Сегмент данных
- ...
- tFloat dt -502556.267e600 ; Вещественное число
- szFloat rb 100 ; Буфер-приемник строки
- ...
- ; Сегмент кода
- ...
- ; Преобразовать вещественное число в строку
- stdcall FloatToString,tFloat,szFloat
- ; szFloat = '-5.02556267e+605'
Просмотров: 11573 | Комментариев: 27
![](/images/dot.gif)
Внимание! Статья опубликована больше года назад, информация могла устареть!
Комментарии
Отзывы посетителей сайта о статье
ManHunter
(03.04.2023 в 21:49):
Добавил случай с -0 и проверку на denormalized number.
![](/images/dot.gif)
ManHunter
(03.04.2023 в 17:56):
Это хорошо, когда глазами наблюдаешь числа в статике. Как только с ними начинаешь что-то делать, то картина резко меняется.
Поправил, спасибо.
При вычислении значение переходит в Inf, так что ограничение по минимальности получается 1e-4916
+-Inf и +-NaN добавлены, есть, наверное, смысл еще добавить Denormalized, тоже вроде как частный случай записи float.
![](/images/dot.gif)
<{[(|)]}>
(29.03.2023 в 21:18):
Тут уже обсуждалось, но всё равно добавлю:
вместо 9.45 отображает 9.4500000000000032
однако вместо 9.4500000000000032 выводится 9.4500000000000064
тут уже явно неверное число выводится
Вот битовое содержимое чисел:
9.45 = 0x4002_9733_3333_3333_3333
9.4500000000000032 = 0x4002_9733_3333_3333_419D
9.4500000000000064 = 0x4002_9733_3333_3333_5006
Это различные числа, они отличаются между собой и не равны друг другу.
Если это происходит из-за потери точности, то в округлении смысла мало.
Действительно, зачем пытаться округлять, если последние цифры всё равно не точны?
Число 9223372036854775807 отображается как 9.2233720368e+18
Здесь только 10 знаков после запятой, но последняя цифра округлена до 8, а не до 9.
Или здесь не происходит округление?
вместо 9.45 отображает 9.4500000000000032
однако вместо 9.4500000000000032 выводится 9.4500000000000064
тут уже явно неверное число выводится
Вот битовое содержимое чисел:
9.45 = 0x4002_9733_3333_3333_3333
9.4500000000000032 = 0x4002_9733_3333_3333_419D
9.4500000000000064 = 0x4002_9733_3333_3333_5006
Это различные числа, они отличаются между собой и не равны друг другу.
Если это происходит из-за потери точности, то в округлении смысла мало.
Действительно, зачем пытаться округлять, если последние цифры всё равно не точны?
Число 9223372036854775807 отображается как 9.2233720368e+18
Здесь только 10 знаков после запятой, но последняя цифра округлена до 8, а не до 9.
Или здесь не происходит округление?
![](/images/dot.gif)
<{[(|)]}>
(29.03.2023 в 19:21):
это было бы здорово :)
похоже, что проблема исчезла после замены цикла на формулу 2^(x*log2(e))
Заметил, что функция портит регистр ebp.
Это происходит из-за того, что идёт обращение за пределы буфера на стеке.
Перед выходом из функции "pop ebp" берёт со стека уже испорченное значение.
Если после объявления всех локальных добавить "local dummy rb 1", то всё работает.
похоже, что проблема исчезла после замены цикла на формулу 2^(x*log2(e))
Заметил, что функция портит регистр ebp.
Это происходит из-за того, что идёт обращение за пределы буфера на стеке.
Перед выходом из функции "pop ebp" берёт со стека уже испорченное значение.
Если после объявления всех локальных добавить "local dummy rb 1", то всё работает.
![](/images/dot.gif)
ManHunter
(28.03.2023 в 13:06):
Конкретно эта - не поддерживает, в другом проекте поддерживает. Может быть добавлю сюда.
Вообще никаких проблем, или нужны конкретные примеры.
Поправлю, спасибо
Починилось после замены цикла
Остальное меня устраивает в нынешнем виде.
![](/images/dot.gif)
<{[(|)]}>
(28.03.2023 в 02:51):
Отсекать незначащие цифры не должно быть сложно: мы ведь уже посчитали и знаем количество цифр в числе.
Эта функция не поддерживает: Nan(не число), -Inf и +Inf(бесконечности), -0.0(отрицательный ноль).
Также были проблемы при выводе чисел, у которых есть только дробная часть, а целая часть равна нулю.
И со слишком мелкими числами(такого порядка 1e-4917) проблемы были.
Ещё по какой-то причине проблема была с "12345678E+9".
Возвести 10 в нужную степень можно и без цикла по формуле Exp(X * Ln(10)).
Где функция Exp(x) — это e^x = 2^(x*log2(e)).
Чем больше экспонента, тем дольше будет выполняться цикл, 4932 раза — это будет долго.
По поводу корректировки стороны округления: интуитивно кажется, что это лишнее, ведь округление должно быть для всех чисел одинаковое к нулю, а не только для положительных.
Возможно, где-то ещё что-то не верно работает, если приходится так округлять.
Может быть, нет необходимости использовать fbstp дважды, а выгрузить целиком все цифры за один раз.
В Masm32 в fpulib также есть более продвинутая функция FpuFLtoA by Raymond Filiatreault.
Возможно, есть смысл позаимствовать что-то из неё.
Было бы удобно иметь функцию, принимающую в качестве параметров ширину(Width) и количество знаков после запятой(Precision).
А пропуск лидирующих нулей экспоненты можно сделать опциональным.
Эта функция не поддерживает: Nan(не число), -Inf и +Inf(бесконечности), -0.0(отрицательный ноль).
Также были проблемы при выводе чисел, у которых есть только дробная часть, а целая часть равна нулю.
И со слишком мелкими числами(такого порядка 1e-4917) проблемы были.
Ещё по какой-то причине проблема была с "12345678E+9".
Возвести 10 в нужную степень можно и без цикла по формуле Exp(X * Ln(10)).
Где функция Exp(x) — это e^x = 2^(x*log2(e)).
Чем больше экспонента, тем дольше будет выполняться цикл, 4932 раза — это будет долго.
По поводу корректировки стороны округления: интуитивно кажется, что это лишнее, ведь округление должно быть для всех чисел одинаковое к нулю, а не только для положительных.
Возможно, где-то ещё что-то не верно работает, если приходится так округлять.
Может быть, нет необходимости использовать fbstp дважды, а выгрузить целиком все цифры за один раз.
В Masm32 в fpulib также есть более продвинутая функция FpuFLtoA by Raymond Filiatreault.
Возможно, есть смысл позаимствовать что-то из неё.
Было бы удобно иметь функцию, принимающую в качестве параметров ширину(Width) и количество знаков после запятой(Precision).
А пропуск лидирующих нулей экспоненты можно сделать опциональным.
![](/images/dot.gif)
ManHunter
(02.02.2015 в 13:51):
Даже близко не по адресу.
![](/images/dot.gif)
Максим
(02.02.2015 в 13:46):
Приветствую! Не знаю на сколько я по адрессу.
Хочу спросить - может ли подойти данная программа для следующей задачи?
Есть файл, в котором есть строки такого вида:
<real>1419191060.9531741</real>
их же нужно привести к виду "02/02/2015 00:31:49"
Или подскажите куда копать?
Хочу спросить - может ли подойти данная программа для следующей задачи?
Есть файл, в котором есть строки такого вида:
<real>1419191060.9531741</real>
их же нужно привести к виду "02/02/2015 00:31:49"
Или подскажите куда копать?
![](/images/dot.gif)
kw33
(24.11.2014 в 08:08):
Думаю, нужны проверки на минус ноль, не числа, бесконечности.
![](/images/dot.gif)
picode
(23.04.2014 в 19:22):
ManHunter, все равно огромное спасибо, попробую грамотно подкорректировать этот момент самостоятельно!
![](/images/dot.gif)
ManHunter
(23.04.2014 в 19:10):
Увы. Можно только уменьшить количество знаков после запятой, за счет этого _в некоторых случаях_ можно убрать лишние знаки. И то под любое количество знаков всегда можно будет найти пример, где лишние значения будут появляться. Одновременно чтобы и шашечки и ехать, такого с вещественными числами не получится. Я и так предусмотрел корректировку стороны округления для разных ситуаций, без этого вообще была бы жопа.
![](/images/dot.gif)
picode
(23.04.2014 в 18:59):
да, проблема была только с нулем) спасибо! С малыми отрицательными величинами сам виноват) Еще хотелось бы уточнить некоторое поведение:
При
tFloat dt 3.22
возвращает
3.2200000000000004
что обусловлено, точностью... но все же, не должно ли оно отсекаться?
При
tFloat dt 3.22
возвращает
3.2200000000000004
что обусловлено, точностью... но все же, не должно ли оно отсекаться?
![](/images/dot.gif)
ManHunter
(23.04.2014 в 07:58):
При нулевом значении глючило. При ненулевом, даже самом мелком, все нормально.
"-1.030340 делим на Pi" выводит -0.3279770810122553
Косяк был здесь:
; Это ноль?
lea esi,[lpFloat]
заменить на
mov esi,[lpFloat]
Статью поправил, пример заменил. Спасибо!
"-1.030340 делим на Pi" выводит -0.3279770810122553
Косяк был здесь:
; Это ноль?
lea esi,[lpFloat]
заменить на
mov esi,[lpFloat]
Статью поправил, пример заменил. Спасибо!
![](/images/dot.gif)
picode
(23.04.2014 в 07:45):
вообще при любых операциях близких к 0, например -1.030340 делим на Pi и пытаемся вывести)
![](/images/dot.gif)
picode
(23.04.2014 в 07:13):
Уточню странное поведение:
;-----------------------------------
finit
fld [evalue]
fld [fvalue]
fadd st0,st1
push fvalue
pop eax
fstp tword [eax]
;-----------------------------------
при
fvalue dt 1.0
evalue dt -2.0
fstring rb 100
работает нормально), а при
fvalue dt 1.0
evalue dt -1.0
fstring rb 100
уже нет...
Не закралась ли ошибка?
;-----------------------------------
finit
fld [evalue]
fld [fvalue]
fadd st0,st1
push fvalue
pop eax
fstp tword [eax]
;-----------------------------------
при
fvalue dt 1.0
evalue dt -2.0
fstring rb 100
работает нормально), а при
fvalue dt 1.0
evalue dt -1.0
fstring rb 100
уже нет...
Не закралась ли ошибка?
![](/images/dot.gif)
Сергей
(03.04.2014 в 20:36):
Подскажите как обратно перевести строку в числовой тип?
![](/images/dot.gif)
ManHunter
(30.03.2014 в 22:24):
Совсем уже со своей копирастией долбанулись? Если что-то выложено в интернете - значит это уже бесплатно. Только шароварщики в любом случае идут нахуй, вот и вся лицензия.
![](/images/dot.gif)
picode
(30.03.2014 в 21:32):
под какой лицензией выложен код?
![](/images/dot.gif)
ManHunter
(30.03.2014 в 17:28):
Когда люди придумали деньги, количество нерешаемых проблем резко сократилось.
![](/images/dot.gif)
Mixer
(30.03.2014 в 17:22):
Если использовать String2float , предварительно увеличив точность в той функции на выходе до ( fstp tword [eax] ) а потом использовать эту функцию, то на выходе ересь (как и в моей голове). Если по отдельности - то нормально. Что требуется сделать, чтобы они были обратносовместимыми?
![](/images/dot.gif)
Mixer
(28.03.2014 в 22:34):
ты увидел мои мольбы! Боже, спасибо!
![](/images/dot.gif)
ManHunter
(15.03.2014 в 16:28):
ЖК, ставь смайлики после шуток, а то мало ли что. Тут, бывает, на полном серьезе такие вопросы задают, что волосы на голове начинают шевелиться.
![](/images/dot.gif)
ЖК
(15.03.2014 в 16:11):
:)
ManHunter, это шутка была, если что. По-моему, это очевидно, когда речь про один и тот же опкод.
На самом деле читаю этот блог и ностальгия пробивает по временам, когда на древнем тасме под дос писали в процессе учебы вот такие примеры. Или прикольные задачки, вроде: есть текстовая консоль (неважно, что в ней - dos, нортон, лексикон); задача: написать ассемблерный резидент, который находил бы "на экране" все символы \ | / - и начинал бы их "крутить", причем размер программы должен был быть наименьший. Эхх... это были ТЕ времена
ManHunter, это шутка была, если что. По-моему, это очевидно, когда речь про один и тот же опкод.
На самом деле читаю этот блог и ностальгия пробивает по временам, когда на древнем тасме под дос писали в процессе учебы вот такие примеры. Или прикольные задачки, вроде: есть текстовая консоль (неважно, что в ней - dos, нортон, лексикон); задача: написать ассемблерный резидент, который находил бы "на экране" все символы \ | / - и начинал бы их "крутить", причем размер программы должен был быть наименьший. Эхх... это были ТЕ времена
![](/images/dot.gif)
ManHunter
(14.03.2014 в 19:45):
ЖК, учи матчасть http://asmworld.ru/spravochnik-komand/pusha/
Опкод один, поведение и дизасм зависит от разрядности процесса
Опкод один, поведение и дизасм зависит от разрядности процесса
![](/images/dot.gif)
ЖК
(14.03.2014 в 19:09):
Про сохранение регистров.
Там не pushad/popad должно быть ? Работаем с 32-бит регистрами, а сохраняем/восстанавливаем только младшие 16 бит.
Там не pushad/popad должно быть ? Работаем с 32-бит регистрами, а сохраняем/восстанавливаем только младшие 16 бит.
![](/images/dot.gif)
Марат
(13.03.2014 в 18:33):
Спасибо
![](/images/dot.gif)
Grey
(13.03.2014 в 13:01):
Спасибо
![](/images/dot.gif)
Добавить комментарий
Заполните форму для добавления комментария
![](/images/dot.gif)