Blog. Just Blog

Преобразование числа в строку с разделением на разряды

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Преобразование числа в строку с разделением на разряды
Преобразование числа в строку с разделением на разряды

Если вы работаете с большими десятичными числами, то наверняка согласитесь, что число с разделением на разряды (то есть с группировкой по три символа: тысячи, миллионы и так далее) воспринимается гораздо лучше, чем просто последовательность цифр. Так проще выявлять ошибки или, например, с одного взгляда можно оценить порядок числа.

Обычно для разделения строки на разряды рекомендуют использовать штатную функцию WinAPI GetNumberFormat. Действительно, с ней очень удобно работать: подали на вход число в виде строки без разбивки и получили на выходе его же, но уже отформатированное по любым заданным правилам. При необходимости эта функция даже умеет округлять дробные числа до нужного количества знаков, что тоже можно использовать при решении некоторых задач. За удобства приходится расплачиваться размером получаемого кода и необходимостью использовать дополнительные структуры и переменные.
  1. ;--------------------------------------------------------------------
  2. ; Процедура преобразования числа в строку с разделением на разряды
  3. ;--------------------------------------------------------------------
  4. ; Параметры:
  5. ;   lpBuff - указатель на строку, куда будет записан результат
  6. ;   dNum - число для преобразования
  7. ;--------------------------------------------------------------------
  8. proc split_num lpBuff:DWORD, dNum:DWORD
  9.  
  10. ; Структура для форматирования числа
  11. struct _NUMBERFMT
  12.         NumDigits       dd ?
  13.         LeadingZero     dd ?
  14.         Grouping        dd ?
  15.         lpDecimalSep    dd ?
  16.         lpThousandSep   dd ?
  17.         NegativeOrder   dd ?
  18. ends
  19.  
  20. ; Локальные переменные
  21. locals
  22.         tmp     rb 16
  23. endl
  24.  
  25.         pusha
  26.  
  27.         ; Преобразовать число в строку
  28.         lea     ebx,[tmp]
  29.         invoke  wsprintf,ebx,.maski,[dNum]
  30.         add     esp,12
  31.  
  32.         ; Выделить память под структуру
  33.         invoke  GetProcessHeap
  34.         mov     esi,eax
  35.         invoke  HeapAlloc,esi,HEAP_ZERO_MEMORY,sizeof._NUMBERFMT
  36.         ; Сохранить указатель для HeapFree
  37.         push    eax
  38.  
  39.         ; Отформатировать строку
  40.         mov     [eax+_NUMBERFMT.NumDigits],0
  41.         mov     [eax+_NUMBERFMT.Grouping],3
  42.         mov     [eax+_NUMBERFMT.lpDecimalSep],.szSep
  43.         mov     [eax+_NUMBERFMT.lpThousandSep],.szSep
  44.         invoke  GetNumberFormat,0,0,ebx,eax,[lpBuff],16
  45.  
  46.         ; Прибраться за собой
  47.         invoke  HeapFree,esi,0
  48.  
  49.         popa
  50.         ret
  51.  
  52. .maski  db '%u',0
  53. .szSep  db ',',0        ; Разделитель разрядов
  54. endp
Параметры функции: lpBuff - указатель на буфер, куда будет записана строка после преобразования числа и разделения его на разряды, dNum - беззнаковое число размером DWORD, которое будет преобразовываться в строку. Мне очень хотелось сделать структуру NUMBERFMT локальной, но при тестировании в некоторых случаях это давало ошибку, поэтому пришлось задействовать динамическое выделение памяти, что еще больше увеличило код.

Более компактный вариант получается, если делать разбивку строки самостоятельно. Тут не надо выделять никакую память, хватает одной локальной переменной. Но, конечно, про всякие автоматические округления и прочую красоту, доступную в GetNumberFormat, придется забыть, хотя для целых чисел это и не актуально.
  1. ;--------------------------------------------------------------------
  2. ; Процедура преобразования числа в строку с разделением на разряды
  3. ;--------------------------------------------------------------------
  4. ; Параметры:
  5. ;   lpBuff - указатель на строку, куда будет записан результат
  6. ;   dNum - число для преобразования
  7. ;--------------------------------------------------------------------
  8. proc split_num lpBuff:DWORD, dNum:DWORD
  9. ; Локальные переменные
  10. locals
  11.    tmp rb 16
  12. endl
  13.  
  14.         pusha
  15.         lea     esi,[tmp]
  16.         ; Строка с разделением на разряды
  17.         invoke  wsprintf,esi,.maski,[dNum]
  18.         add     esp,12
  19.  
  20.         xor     edx,edx
  21.         mov     ecx,3
  22.         div     ecx
  23.         mov     ecx,edx
  24.  
  25.         mov     ebx,eax
  26.         inc     ebx
  27.  
  28.         mov     edi,[lpBuff]
  29.         cld
  30. .loc:
  31.         rep     movsb
  32.  
  33.         ; Разделитель разрядов
  34.         or      edx,edx
  35.         jz      @f
  36.         mov     al,','
  37.         stosb
  38. @@:
  39.         inc     edx
  40.         mov     ecx,3
  41.         dec     ebx
  42.         or      ebx,ebx
  43.         jnz     .loc
  44.  
  45.         dec     edi
  46.         mov     al,0
  47.         stosb
  48.  
  49.         popa
  50.         ret
  51.  
  52. .maski  db '%u',0
  53. endp
Немного поразмыслив, я решил сделать еще один вариант - вообще без использования WinAPI. В результате получился самый компактный вариант преобразования, не требующий выделения памяти и локальных переменных. В своих программах я использую именно его.
  1. ;--------------------------------------------------------------------
  2. ; Процедура преобразования числа в строку с разделением на разряды
  3. ;--------------------------------------------------------------------
  4. ; Параметры:
  5. ;   lpBuff - указатель на строку, куда будет записан результат
  6. ;   dNum - число для преобразования
  7. ;--------------------------------------------------------------------
  8. proc split_num lpBuff:DWORD, dNum:DWORD
  9.         pusha
  10.  
  11.         mov     eax,[dNum]
  12.         xor     esi,esi
  13.         xor     ecx,ecx
  14.         push    ecx
  15.         inc     ecx
  16. .loc_loop:
  17.         ; Получить следующую цифру
  18.         xor     edx,edx
  19.         mov     ebx,10
  20.         div     ebx
  21.         add     dl,'0'
  22.         push    edx
  23.         inc     ecx
  24.  
  25.         ; Все цифры сохранили?
  26.         or      eax,eax
  27.         jz      .loc_store
  28.  
  29.         ; Нужен разделитель разрядов?
  30.         inc     esi
  31.         cmp     esi,3
  32.         jne     @f
  33.         mov     dl,','
  34.         push    edx
  35.         inc     ecx
  36.         xor     esi,esi
  37. @@:
  38.         jmp     .loc_loop
  39.  
  40. .loc_store:
  41.         mov     edi,[lpBuff]
  42.         cld
  43. @@:
  44.         pop     eax
  45.         stosb
  46.         loop    @b
  47.  
  48.         popa
  49.         ret
  50. endp
В приложении примеры программ с исходными текстами, реализующие все три приведенные в статье алгоритма. Кроме тестового числа они выводят размер кода используемой процедуры.

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

Split.Number.Demo.zip (4,979 bytes)


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

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

Комментарии

Отзывы посетителей сайта о статье
user (18.01.2016 в 21:00):
Да, похоже.
На мой взгляд, самый предпочтительный вариант.
Оставляет простор для действий.

)) Сконвертировал в 32-bit FASM из старинной 16-bit TASM процедуры.
Специально слазил в архивы.
ManHunter (18.01.2016 в 00:52):
Похоже на мой второй вариант.
user (17.01.2016 в 20:01):
..мда. Ну, по теме топика:
Как делал раньше такое - конвертировал число в обычную строку,
а потом натравливал на эту строку процедуру, работающую чисто с текстом.
Вот, сконвертировал в FASM ту процедуру:
rghost.ru/6dTtXZsfQ
brute (17.01.2016 в 12:08):
вариант на PB:
s.s=Str(1234567874474354532)
l=Len(s)
i=2
While l>(i+1)
  s = InsertString(s, "_|_", l-i)
  i=i+3
Wend
  MessageRequester("",s)
insolor (17.01.2016 в 06:06):
> Только вот по поводу кириллицы в комментариях -  она в UTF-8 (cp855)
UTF-8 - это не cp855. Как-то даже совсем не рядом.

> Вооюще, все двухбайтовые кодировки необходимы для тех языков, в которых больше 80h символов
А еще для программ, поддерживающих несколько языков.
addhaloka (17.01.2016 в 04:47):
ЦитатаТолько сразу вопрос, -  а если набирать строковые переменные в кириллице UTF-8, то как на это будут реагировать APIA/APIW функции?

APIW нормально реагируют, достаточно подключить UTF8.INC.
user (16.01.2016 в 20:13):
Так в коииентариях может быть вообще любой набор символов - они все игнорируются ассемблером. вообще любой Аsm на такие вещи и не должен реанировать.

Впрочем, это и не юникод - настоящий юникод (2 байта на любой символ) FASM не обрабатывает. Ну, это в порядке оффтопа. Можно удалить..
Это не проблема.

Вооюще, все двухбайтовые кодировки необходимы для тех языков, в которых больше 80h символов, славянские сюда не относятся, - для них достаточно и DOS866 или WIN1251 кодировок. (Как и для инглиша, который в любой из них присутствует).

.. Только сразу вопрос, -  а если набирать строковые переменные в кириллице UTF-8, то как на это будут реагировать APIA/APIW функции?
Всё равно ведь придётся их перед использованием конвертировать либо в ANSI, либо в юникод?
ManHunter (16.01.2016 в 06:57):
Давно уже перешел на сборку всех FASM'овских проектов в Sublime Text, там никаких проблем с кодировкой нет. Компилятор тоже против юникода не возражает.
user (16.01.2016 в 00:59):
Только вот по поводу кириллицы в комментариях -  она в UTF-8 (cp855),
а дучше бы в ANSI (cp1251)

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

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

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