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 11h
  17.         local   tmp2 rb 11h
  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],0
  32.         jne     loc_not_zero
  33.         ; Записать в строку ноль
  34.         mov     al,'0'
  35.         stosb
  36.         jmp     loc_ret
  37.  
  38. loc_not_zero:
  39.         ; Скопировать число в локальную переменную
  40.         push    edi
  41.         mov     esi,[lpFloat]
  42.         lea     edi,[saved_float]
  43.         movsd
  44.         movsd
  45.         movsw
  46.         pop     edi
  47.         ; Число отрицательное?
  48.         cmp     dword [saved_float+6],0
  49.         jge     loc_not_signed
  50.         ; Привести число к абсолютному значению
  51.         and     byte [saved_float+9],7Fh
  52.         ; Записать в строку минус
  53.         mov     al,'-'
  54.         stosb
  55.  
  56. loc_not_signed:
  57.         ; Проверить число на наличие дробной части и
  58.         ; подсчитать количество цифр в нем
  59.         fclex
  60.         ; Сохранить управляющее слово
  61.         fstcw   [old_cw]
  62.         ; Установить управляющее слово
  63.         mov     [new_cw],0000001001111111b
  64.         fldcw   [new_cw]
  65.         lea     esi,[saved_float]
  66.         fld     tbyte [esi]
  67.         fld     st
  68.         ; Выделить мантиссу и порядок
  69.         fxtract
  70.         fstp    st
  71.         fldlg2
  72.         ; Получить количество цифр в числе
  73.         fmulp   st1,st
  74.         fistp   [digits_count]
  75.         ; Если цифр больше 16, то число отображается в
  76.         ; нормализованном виде с мантиссой и экспонентой
  77.         cmp     [digits_count],10h
  78.         jnb     loc_not_integer
  79.         ; У числа есть дробная часть?
  80.         fld     st
  81.         frndint
  82.         fcomp   st1
  83.         fstsw   ax
  84.         test    ah,01000000b
  85.         ; Да, отображать число с дробной частью
  86.         jz      loc_not_integer
  87.  
  88.         ; Целое число без дробной части и экспоненты
  89.         lea     eax,[tmp1]
  90.         fbstp   [eax]
  91.  
  92.         ; Перевести BCD-число в строку
  93.         push    edi
  94.         lea     esi,[tmp1+8]
  95.         lea     edi,[tmp2]
  96.         mov     ecx, 9
  97. @@:
  98.         std
  99.         xor     eax,eax
  100.         lodsb
  101.         cld
  102.         rol     ax,12
  103.         rol     ah,4
  104.         add     ax,'00'
  105.         stosw
  106.         loop    @b
  107.         pop     edi
  108.  
  109.         ; Пропустить лидирующий ноль
  110.         mov     eax,11h
  111.         mov     ecx,[digits_count]
  112.         sub     eax,ecx
  113.         inc     ecx
  114.         lea     esi,[tmp2+eax]
  115.         cmp     byte [esi],'0'
  116.         jne     @f
  117.         inc     esi
  118.         dec     ecx
  119. @@:
  120.         ; Перенести полученное число из временного буфера
  121.         rep     movsb
  122.         jmp     loc_clear_stack
  123.  
  124. loc_not_integer:
  125.         mov     eax,10h
  126.         sub     eax,[digits_count]
  127.  
  128.         ; Преобразовать число в целое до 16 разрядов
  129.         mov     ecx,eax
  130.         cmp     eax,0
  131.         jge     @f
  132.         neg     eax
  133. @@:
  134.         ; Для чисел больше 0 корректировка округления в сторону 0
  135.         mov     [new_cw],0000101001111111b
  136.         cmp     ecx,0
  137.         jge     @f
  138.         mov     [new_cw],0000011001111111b
  139. @@:
  140.         ; Установить управляющее слово
  141.         fldcw   [new_cw]
  142.  
  143.         ; Возвести 10 в степень количества цифр
  144.         mov     [tmp],eax
  145.         fild    [tmp]
  146.         fld     [float2]
  147.         fyl2x
  148.         fld1
  149.         fld     st1
  150.         fprem
  151.         f2xm1
  152.         faddp
  153.         fscale
  154.  
  155.         ; Почистить стек
  156.         fxch    st1
  157.         fstp    st
  158.  
  159.         ; Если число меньше 0, то умножить, иначе разделить
  160.         cmp     ecx,0
  161.         jge     @f
  162.         fdivp   st1,st
  163.         jmp     loc_rounded
  164. @@:
  165.         fmulp   st1,st
  166.  
  167. loc_rounded:
  168.         ; Полученное значение меньше 1.0e16 ?
  169.         fcom    [float1]
  170.         fstsw   ax
  171.         test    ah,1
  172.         jz      @f
  173.         fmul    [float2]
  174.         dec     [digits_count]
  175. @@:
  176.         ; Целое число без дробной части и экспоненты
  177.         lea     eax,[tmp1]
  178.         fbstp   [eax]
  179.  
  180.         ; Перевести BCD-число в строку
  181.         push    edi
  182.         lea     esi,[tmp1+8]
  183.         lea     edi,[tmp2]
  184.         mov     ecx, 9
  185. @@:
  186.         std
  187.         xor     eax,eax
  188.         lodsb
  189.         cld
  190.         rol     ax,12
  191.         rol     ah,4
  192.         add     ax,'00'
  193.         stosw
  194.         loop    @b
  195.         pop     edi
  196.  
  197.         ; Числу требуется мантисса и экспонента?
  198.         lea     esi,[tmp2+1]
  199.         mov     ecx,[digits_count]
  200.         cmp     ecx,-0Fh
  201.         jl      loc_mantiss_and_exponent
  202.         cmp     ecx,10h
  203.         jg      loc_mantiss_and_exponent
  204.  
  205.         ; Заполнить дробную часть числа
  206.         inc     ecx
  207.         cmp     ecx,0
  208.         jg      @f
  209.         mov     ax,'0.'
  210.         stosw
  211.         neg     ecx
  212.         mov     al,'0'
  213.         rep     stosb
  214.         mov     ecx,10h
  215.         jmp     loc_fraction_filled
  216. @@:
  217.         rep     movsb
  218.         mov     al,'.'
  219.         stosb
  220.         mov     ecx,10h
  221.         sub     ecx,[digits_count]
  222.  
  223. loc_fraction_filled:
  224.         rep     movsb
  225.         jmp     @f
  226.  
  227. loc_clear_fraction:
  228.         ; Удалить завершающие нули дробной части
  229.         dec     edi
  230. @@:
  231.         cmp     byte [edi-1],'0'
  232.         jz      loc_clear_fraction
  233.         cmp     byte [edi-1],'.'
  234.         jnz     @f
  235.         dec     edi
  236. @@:
  237.         jmp     loc_clear_stack
  238.  
  239. loc_mantiss_and_exponent:
  240.         ; Дробная часть мантиссы
  241.         movsb
  242.         mov     al,'.'
  243.         stosb
  244.         movsd
  245.         movsd
  246.         movsw
  247.         ; Удалить завершающие нули дробной части
  248. @@:
  249.         cmp     byte [edi-1],'0'
  250.         jne     @f
  251.         cmp     byte [edi-2],'.'
  252.         je      @f
  253.         dec     edi
  254.         jmp     @b
  255. @@:
  256.         ; Символ и знак экспоненты
  257.         mov     al,'e'
  258.         stosb
  259.         mov     al,'+'
  260.         mov     ebx,[digits_count]
  261.         cmp     ebx, 0
  262.         jge     @f
  263.         mov     al,'-'
  264.         neg     ebx
  265. @@:
  266.         stosb
  267.  
  268.         ; Значение экспоненты
  269.         mov     eax,ebx
  270.         mov     ecx,10
  271.         mov     ebx,4
  272. @@:
  273.         dec     ebx
  274.         xor     edx,edx
  275.         div     ecx
  276.         add     dl,'0'
  277.         mov     [tmp1+ebx],dl
  278.         or      ebx,ebx
  279.         jnz     @b
  280.  
  281.         ; Пропустить лидирующие нули экспоненты
  282.         mov     ecx,4
  283.         lea     esi,[tmp1]
  284. @@:
  285.         lodsb
  286.         cmp     al,'0'
  287.         jne     @f
  288.         dec     ecx
  289.         jmp     @b
  290. @@:
  291.         dec     esi
  292.         rep     movsb
  293.  
  294. loc_clear_stack:
  295.         ; Восстановить управляющее слово
  296.         fldcw   [old_cw]
  297. loc_ret:
  298.         ; Окончание строки
  299.         mov     al,0
  300.         stosb
  301.  
  302.         ; Восстановить все регистры
  303.         popa
  304.         ret
  305.  
  306. float1  dq      1.0e16
  307. float2  dq      10.0
  308.  
  309. 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)


Поделиться ссылкой ВКонтакте Поделиться ссылкой на Facebook Поделиться ссылкой на LiveJournal Поделиться ссылкой в Мой Круг Добавить в Мой мир Добавить на ЛиРу (Liveinternet) Добавить в закладки Memori Добавить в закладки Google
Просмотров: 10530 | Комментариев: 27

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

Комментарии

Отзывы посетителей сайта о статье
ManHunter (31.03.2023 в 10:34):
Повторно ПРОШУ сменить ник. Это можно сделать или почистив куки, или написав каммент под другим ником. Иначе с каждого захода мне валится на почту пачка уведомлений от скрипта безопасности.
ManHunter (29.03.2023 в 21:37):
Ник лучше сменить, пока мне не надоело чистить уведомления от системы безопасности сайта.
<{[(|)]}> (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-2023
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.08 сек. / MySQL: 2 (0.0061 сек.) / Память: 5 Mb
Наверх