Blog. Just Blog

Преобразование вещественного числа в строку на Ассемблере

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Ранее я выкладывал статью о том, как на Ассемблере перевести строку в вещественное число, теперь настало время произвести обратную операцию, то есть перевести вещественное число в строку. В отличие от целых чисел, где достаточно нескольких строк кода, с преобразованием вещественных чисел пришлось повозиться. В результате появилась следующая функция. За основу взята аналогичная функция из пакета MASM, но со значительными доработками.
  1. ;------------------------------------------------------------
  2. ; Функция перевода вещественного числа в строку
  3. ;------------------------------------------------------------
  4. ; Портирование с MASM, доработка и оптимизация - ManHunter
  5. ; Параметры:
  6. ;   lpFloat - указатель на вещественное число TBYTE
  7. ;   lpResult - указатель на строку-приемник результата
  8. ;------------------------------------------------------------
  9. proc    FloatToString lpFloat:DWORD, lpResult:DWORD
  10.         ; Локальные переменные
  11.         local   digits_count:DWORD
  12.         local   tmp:DWORD
  13.         local   old_cw:WORD
  14.         local   new_cw:WORD
  15.         local   saved_float:TBYTE
  16.         local   tmp1 rb 12h
  17.         local   tmp2 rb 12h
  18.  
  19.         ; Сохранить все регистры
  20.         pusha
  21.  
  22.         ; Указатель на строку-приемник
  23.         mov     edi,[lpResult]
  24.  
  25.         ; Это ноль?
  26.         mov     esi,[lpFloat]
  27.         cmp     dword [esi],0
  28.         jne     loc_not_zero
  29.         cmp     dword [esi+4],0
  30.         jne     loc_not_zero
  31.         cmp     word [esi+8],0x8000
  32.         je      loc_minus_zero
  33.         cmp     word [esi+8],0
  34.         jne     loc_not_zero
  35.         ; Записать в строку ноль
  36.         mov     al,'0'
  37.         stosb
  38.         jmp     loc_ret
  39. loc_minus_zero:
  40.         ; Записать в строку минус ноль
  41.         mov     ax,'-0'
  42.         stosw
  43.         jmp     loc_ret
  44. loc_not_zero:
  45.         ; Denormalized?
  46.         mov     ax,word [esi+8]
  47.         and     ax,7F80h
  48.         or      ax,ax
  49.         jnz     loc_not_denorm
  50.         cmp     dword [esi],0
  51.         jne     loc_denorm
  52.         cmp     dword [esi+4],0
  53.         je      loc_not_denorm
  54. loc_denorm:
  55.         mov     esi,szDen
  56.         movsd
  57.         movsd
  58.         movsd
  59.         jmp     loc_ret
  60. loc_not_denorm:
  61.         ; Infinity или NaN?
  62.         mov     ax,word [esi+8]
  63.         and     ax,7F80h
  64.         cmp     ax,7F80h
  65.         jne     loc_not_inf_nan
  66.  
  67.         ; Это Infinity?
  68.         cmp     dword [esi],0
  69.         jne     loc_nan
  70.         cmp     dword [esi+4],80000000h
  71.         jne     loc_nan
  72. loc_infinity:
  73.         mov     esi,szInf
  74.         movsd
  75.         movsb
  76. loc_plus_minus:
  77.         movsd
  78.         mov     esi,[lpFloat]
  79.         test    byte [esi+9],80h
  80.         jz      loc_ret
  81.         mov     esi,[lpResult]
  82.         mov     byte[esi],'-'
  83.         jmp     loc_ret
  84. loc_nan:
  85.         mov     esi,szNan
  86.         jmp     loc_plus_minus
  87.  
  88. loc_not_inf_nan:
  89.         ; Скопировать число в локальную переменную
  90.         push    edi
  91.         mov     esi,[lpFloat]
  92.         lea     edi,[saved_float]
  93.         movsd
  94.         movsd
  95.         movsw
  96.         pop     edi
  97.         ; Число отрицательное?
  98.         cmp     dword [saved_float+6],0
  99.         jge     loc_not_signed
  100.         ; Привести число к абсолютному значению
  101.         and     byte [saved_float+9],7Fh
  102.         ; Записать в строку минус
  103.         mov     al,'-'
  104.         stosb
  105.  
  106. loc_not_signed:
  107.         ; Проверить число на наличие дробной части и
  108.         ; подсчитать количество цифр в нем
  109.         fclex
  110.         ; Сохранить управляющее слово
  111.         fstcw   [old_cw]
  112.         ; Установить управляющее слово
  113.         mov     [new_cw],0000001001111111b
  114.         fldcw   [new_cw]
  115.         lea     esi,[saved_float]
  116.         fld     tbyte [esi]
  117.         fld     st
  118.         ; Выделить мантиссу и порядок
  119.         fxtract
  120.         fstp    st
  121.         fldlg2
  122.         ; Получить количество цифр в числе
  123.         fmulp   st1,st
  124.         fistp   [digits_count]
  125.         ; Если цифр больше 16, то число отображается в
  126.         ; нормализованном виде с мантиссой и экспонентой
  127.         cmp     [digits_count],10h
  128.         jnb     loc_not_integer
  129.         ; У числа есть дробная часть?
  130.         fld     st
  131.         frndint
  132.         fcomp   st1
  133.         fstsw   ax
  134.         test    ah,01000000b
  135.         ; Да, отображать число с дробной частью
  136.         jz      loc_not_integer
  137.  
  138.         ; Целое число без дробной части и экспоненты
  139.         lea     eax,[tmp1]
  140.         fbstp   [eax]
  141.  
  142.         ; Перевести BCD-число в строку
  143.         push    edi
  144.         lea     esi,[tmp1+8]
  145.         lea     edi,[tmp2]
  146.         mov     ecx, 9
  147. @@:
  148.         std
  149.         xor     eax,eax
  150.         lodsb
  151.         cld
  152.         rol     ax,12
  153.         rol     ah,4
  154.         add     ax,'00'
  155.         stosw
  156.         loop    @b
  157.         pop     edi
  158.  
  159.         ; Пропустить лидирующий ноль
  160.         mov     eax,11h
  161.         mov     ecx,[digits_count]
  162.         sub     eax,ecx
  163.         inc     ecx
  164.         lea     esi,[tmp2+eax]
  165.         cmp     byte [esi],'0'
  166.         jne     @f
  167.         inc     esi
  168.         dec     ecx
  169. @@:
  170.         ; Перенести полученное число из временного буфера
  171.         rep     movsb
  172.         jmp     loc_clear_stack
  173.  
  174. loc_not_integer:
  175.         mov     eax,10h
  176.         sub     eax,[digits_count]
  177.  
  178.         ; Преобразовать число в целое до 16 разрядов
  179.         mov     ecx,eax
  180.         cmp     eax,0
  181.         jge     @f
  182.         neg     eax
  183. @@:
  184.         ; Для чисел больше 0 корректировка округления в сторону 0
  185.         mov     [new_cw],0000101001111111b
  186.         cmp     ecx,0
  187.         jge     @f
  188.         mov     [new_cw],0000011001111111b
  189. @@:
  190.         ; Установить управляющее слово
  191.         fldcw   [new_cw]
  192.  
  193.         ; Возвести 10 в степень количества цифр
  194.         mov     [tmp],eax
  195.         fild    [tmp]
  196.         fld     [float2]
  197.         fyl2x
  198.         fld1
  199.         fld     st1
  200.         fprem
  201.         f2xm1
  202.         faddp
  203.         fscale
  204.  
  205.         ; Почистить стек
  206.         fxch    st1
  207.         fstp    st
  208.  
  209.         ; Если число меньше 0, то умножить, иначе разделить
  210.         cmp     ecx,0
  211.         jge     @f
  212.         fdivp   st1,st
  213.         jmp     loc_rounded
  214. @@:
  215.         fmulp   st1,st
  216.  
  217. loc_rounded:
  218.         ; Полученное значение меньше 1.0e16 ?
  219.         fcom    [float1]
  220.         fstsw   ax
  221.         test    ah,1
  222.         jz      @f
  223.         fmul    [float2]
  224.         dec     [digits_count]
  225. @@:
  226.         ; Целое число без дробной части и экспоненты
  227.         lea     eax,[tmp1]
  228.         fbstp   [eax]
  229.  
  230.         ; Перевести BCD-число в строку
  231.         push    edi
  232.         lea     esi,[tmp1+8]
  233.         lea     edi,[tmp2]
  234.         mov     ecx, 9
  235. @@:
  236.         std
  237.         xor     eax,eax
  238.         lodsb
  239.         cld
  240.         rol     ax,12
  241.         rol     ah,4
  242.         add     ax,'00'
  243.         stosw
  244.         loop    @b
  245.         pop     edi
  246.  
  247.         ; Числу требуется мантисса и экспонента?
  248.         lea     esi,[tmp2+1]
  249.         mov     ecx,[digits_count]
  250.         cmp     ecx,-0Fh
  251.         jl      loc_mantiss_and_exponent
  252.         cmp     ecx,10h
  253.         jg      loc_mantiss_and_exponent
  254.  
  255.         ; Заполнить дробную часть числа
  256.         inc     ecx
  257.         cmp     ecx,0
  258.         jg      @f
  259.         mov     ax,'0.'
  260.         stosw
  261.         neg     ecx
  262.         mov     al,'0'
  263.         rep     stosb
  264.         mov     ecx,10h
  265.         jmp     loc_fraction_filled
  266. @@:
  267.         rep     movsb
  268.         mov     al,'.'
  269.         stosb
  270.         mov     ecx,10h
  271.         sub     ecx,[digits_count]
  272.  
  273. loc_fraction_filled:
  274.         rep     movsb
  275.         jmp     @f
  276.  
  277. loc_clear_fraction:
  278.         ; Удалить завершающие нули дробной части
  279.         dec     edi
  280. @@:
  281.         cmp     byte [edi-1],'0'
  282.         jz      loc_clear_fraction
  283.         cmp     byte [edi-1],'.'
  284.         jnz     @f
  285.         dec     edi
  286. @@:
  287.         jmp     loc_clear_stack
  288.  
  289. loc_mantiss_and_exponent:
  290.         ; Дробная часть мантиссы
  291.         movsb
  292.         mov     al,'.'
  293.         stosb
  294.         movsd
  295.         movsd
  296.         movsw
  297.         ; Удалить завершающие нули дробной части
  298. @@:
  299.         cmp     byte [edi-1],'0'
  300.         jne     @f
  301.         cmp     byte [edi-2],'.'
  302.         je      @f
  303.         dec     edi
  304.         jmp     @b
  305. @@:
  306.         ; Символ и знак экспоненты
  307.         mov     al,'e'
  308.         stosb
  309.         mov     al,'+'
  310.         mov     ebx,[digits_count]
  311.         cmp     ebx, 0
  312.         jge     @f
  313.         mov     al,'-'
  314.         neg     ebx
  315. @@:
  316.         stosb
  317.  
  318.         ; Значение экспоненты
  319.         mov     eax,ebx
  320.         mov     ecx,10
  321.         mov     ebx,4
  322. @@:
  323.         dec     ebx
  324.         xor     edx,edx
  325.         div     ecx
  326.         add     dl,'0'
  327.         mov     [tmp1+ebx],dl
  328.         or      ebx,ebx
  329.         jnz     @b
  330.  
  331.         ; Пропустить лидирующие нули экспоненты
  332.         mov     ecx,4
  333.         lea     esi,[tmp1]
  334. @@:
  335.         lodsb
  336.         cmp     al,'0'
  337.         jne     @f
  338.         dec     ecx
  339.         jmp     @b
  340. @@:
  341.         dec     esi
  342.         rep     movsb
  343.  
  344. loc_clear_stack:
  345.         ; Восстановить управляющее слово
  346.         fldcw   [old_cw]
  347. loc_ret:
  348.         ; Окончание строки
  349.         mov     al,0
  350.         stosb
  351.  
  352.         ; Восстановить все регистры
  353.         popa
  354.         ret
  355.  
  356. float1  dq      1.0e16
  357. float2  dq      10.0
  358.  
  359. szInf   db      '+Infinity'
  360. szNan   db      '+NaN'
  361. szDen   db      'Denormalized'
  362.  
  363. endp
Параметры вызова: lpFloat - указатель на вещественное число TBYTE (10 байт), lpResult - указатель на строку-приемник результата. Функция самодостаточная, не требует никаких дополнительных переменных в секции данных и вспомогательных функций, исходное число не меняется. В отличие от MASM'овской функции, поддерживаются вещественные числа в диапазоне -1e4932..+1e4932, увеличено количество отображаемых цифр после запятой, улучшена точность округления мантиссы для очень больших и очень маленьких чисел, улучшено форматирование значения экспоненты и выполнены другие действия по оптимизации алгоритма.

Пример вызова функции перевода вещественного числа в строку. Обратите внимание, что исходное вещественное число в данных описано в ненормализованном виде.
  1. ; Сегмент данных
  2. ...
  3. tFloat  dt -502556.267e600  ; Вещественное число
  4. szFloat rb 100              ; Буфер-приемник строки
  5. ...
  6. ; Сегмент кода
  7. ...
  8.         ; Преобразовать вещественное число в строку
  9.         stdcall FloatToString,tFloat,szFloat
  10.         ; szFloat = '-5.02556267e+605'
В приложении пример программы с исходным текстом, использующей функцию преобразования вещественного числа в строку.

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

Float.To.String.Demo.zip (3,455 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 11254 | Комментариев: 27

Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
ManHunter (03.04.2023 в 21:49):
Добавил случай с -0 и проверку на denormalized number.
ManHunter (03.04.2023 в 17:56):
ЦитатаЭто различные числа, они отличаются между собой и не равны друг другу

Это хорошо, когда глазами наблюдаешь числа в статике. Как только с ними начинаешь что-то делать, то картина резко меняется.
ЦитатаЗаметил, что функция портит регистр ebp

Поправил, спасибо.
ЦитатаИ со слишком мелкими числами(такого порядка 1e-4917) проблемы были.

При вычислении значение переходит в Inf, так что ограничение по минимальности получается 1e-4916

+-Inf и +-NaN добавлены, есть, наверное, смысл еще добавить Denormalized, тоже вроде как частный случай записи float.
<{[(|)]}> (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
Это различные числа, они отличаются между собой и не равны друг другу.
Если это происходит из-за потери точности, то в округлении смысла мало.
Действительно, зачем пытаться округлять, если последние цифры всё равно не точны?

Цитата; Если цифр больше 16, то число отображается в
        ; нормализованном виде с мантиссой и экспонентой
   
Число 9223372036854775807 отображается как 9.2233720368e+18
Здесь только 10 знаков после запятой, но последняя цифра округлена до 8, а не до 9.
Или здесь не происходит округление?
<{[(|)]}> (29.03.2023 в 19:21):
ЦитатаМожет быть добавлю сюда.
это было бы здорово :)
ЦитатаВообще никаких проблем, или нужны конкретные примеры.
похоже, что проблема исчезла после замены цикла на формулу 2^(x*log2(e))

Заметил, что функция портит регистр ebp.
Это происходит из-за того, что идёт обращение за пределы буфера на стеке.
Перед выходом из функции "pop ebp" берёт со стека уже испорченное значение.
Если после объявления всех локальных добавить "local dummy rb 1", то всё работает.
ManHunter (28.03.2023 в 13:06):
ЦитатаЭта функция не поддерживает: Nan(не число), -Inf и +Inf(бесконечности), -0.0(отрицательный ноль).

Конкретно эта - не поддерживает, в другом проекте поддерживает. Может быть добавлю сюда.
ЦитатаТакже были проблемы при выводе чисел, у которых есть только дробная часть, а целая часть равна нулю.

Вообще никаких проблем, или нужны конкретные примеры.
ЦитатаИ со слишком мелкими числами(такого порядка 1e-4917) проблемы были.

Поправлю, спасибо
ЦитатаЕщё по какой-то причине проблема была с "12345678E+9".

Починилось после замены цикла

Остальное меня устраивает в нынешнем виде.
<{[(|)]}> (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).
А пропуск лидирующих нулей экспоненты можно сделать опциональным.
ManHunter (02.02.2015 в 13:51):
Даже близко не по адресу.
Максим (02.02.2015 в 13:46):
Приветствую! Не знаю на сколько я по адрессу.
Хочу спросить - может ли подойти данная программа для следующей задачи?
Есть файл, в котором есть строки такого вида:
<real>1419191060.9531741</real>
их же нужно привести к виду "02/02/2015 00:31:49"
Или подскажите куда копать?
kw33 (24.11.2014 в 08:08):
Думаю, нужны проверки на минус ноль, не числа, бесконечности.
picode (23.04.2014 в 19:22):
ManHunter, все равно огромное спасибо, попробую грамотно подкорректировать этот момент самостоятельно!
ManHunter (23.04.2014 в 19:10):
Увы. Можно только уменьшить количество знаков после запятой, за счет этого _в некоторых случаях_ можно убрать лишние знаки. И то под любое количество знаков всегда можно будет найти пример, где лишние значения будут появляться. Одновременно чтобы и шашечки и ехать, такого с вещественными числами не получится. Я и так предусмотрел корректировку стороны округления для разных ситуаций, без этого вообще была бы жопа.
picode (23.04.2014 в 18:59):
да, проблема была только с нулем) спасибо! С малыми отрицательными величинами сам виноват) Еще хотелось бы уточнить некоторое поведение:
При
tFloat     dt 3.22
возвращает
3.2200000000000004
что обусловлено, точностью... но все же, не должно ли оно отсекаться?
ManHunter (23.04.2014 в 07:58):
При нулевом значении глючило. При ненулевом, даже самом мелком, все нормально.

"-1.030340 делим на Pi" выводит -0.3279770810122553

Косяк был здесь:

; Это ноль?
lea     esi,[lpFloat]
заменить на
mov     esi,[lpFloat]

Статью поправил, пример заменил. Спасибо!
picode (23.04.2014 в 07:45):
вообще при любых операциях близких к 0, например -1.030340 делим на Pi и пытаемся вывести)
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
уже нет...

Не закралась ли ошибка?
Сергей (03.04.2014 в 20:36):
Подскажите как обратно перевести строку в числовой тип?
ManHunter (30.03.2014 в 22:24):
Совсем уже со своей копирастией долбанулись? Если что-то выложено в интернете - значит это уже бесплатно. Только шароварщики в любом случае идут нахуй, вот и вся лицензия.
picode (30.03.2014 в 21:32):
под какой лицензией выложен код?
ManHunter (30.03.2014 в 17:28):
Когда люди придумали деньги, количество нерешаемых проблем резко сократилось.
Mixer (30.03.2014 в 17:22):
Если использовать String2float , предварительно увеличив точность в той функции на выходе до ( fstp tword [eax] ) а потом использовать эту функцию, то на выходе ересь (как и в моей голове). Если по отдельности - то нормально. Что требуется сделать, чтобы они были обратносовместимыми?
Mixer (28.03.2014 в 22:34):
ты увидел мои мольбы! Боже, спасибо!
ManHunter (15.03.2014 в 16:28):
ЖК, ставь смайлики после шуток, а то мало ли что. Тут, бывает, на полном серьезе такие вопросы задают, что волосы на голове начинают шевелиться.
ЖК (15.03.2014 в 16:11):
:)
ManHunter, это шутка была, если что. По-моему, это очевидно, когда речь про один и тот же опкод.

На самом деле читаю этот блог и ностальгия пробивает по временам, когда на древнем тасме под дос писали в процессе учебы вот такие примеры. Или прикольные задачки, вроде: есть текстовая консоль (неважно, что в ней - dos, нортон, лексикон); задача: написать ассемблерный резидент, который находил бы "на экране" все символы \ | / - и начинал бы их "крутить", причем размер программы должен был быть наименьший. Эхх... это были ТЕ времена
ManHunter (14.03.2014 в 19:45):
ЖК, учи матчасть http://asmworld.ru/spravochnik-komand/pusha/
Опкод один, поведение и дизасм зависит от разрядности процесса
ЖК (14.03.2014 в 19:09):
Про сохранение регистров.
Там не pushad/popad должно быть ? Работаем с 32-бит регистрами, а сохраняем/восстанавливаем только младшие 16 бит.
Марат (13.03.2014 в 18:33):
Спасибо
Grey (13.03.2014 в 13:01):
Спасибо

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.07 сек. / MySQL: 2 (0.0054 сек.) / Память: 4.5 Mb
Наверх