![]() | ![]() ![]() ![]() |
ulong2ascii converts a 32-bit unsigned integer into a decimal ASCIIZ string (a null terminated string as used by the C language). A two part algorithm is used. Using multiplication with the reciprocal, the integer is divided by 1e9. The quotient represents the most significant decimal digit. The remainder, which is a number < 1e9, is then converted into a scaled integer representation where 2^28 represents 1. In each step, the integer part is extracted and forms the next decimal digit and the remaining fraction is then multiplied by 10 in preparation for the extarction of the next decimal digit.
Define a symbol FASTMUL if the target CPU has a fast integer multiply (5 cycles or less), e.g. K6 and PentiumII. For CPUs with slow integer multiply such as PentiumMMX, undefine FASTMUL to replace one of the multiplies with a table look up. Note that the table lookup might cause cache misses.
This routine has been timed at 91 cycles on PentiumII with FASTMUL defined, and at 98 cycles on a PentiumMMX with FASTMUL undefined, and cache hits during table lookup. Timing is independent of the magnitude of the input.
This code has been tested exhaustively against the sprintf routines of both the Microsoft Visual C and the Watcom C compiler.
; ; ulong2ascii ; ; input: ; eax = unsigned long to be converted to ASCIIZ string ; edi = pointer to character buffer which receives result (at least 11 chars) ; ; output: ; none (buffer filled with numbers) ; ; destroys: ; eax, ebx, ecx, edx, esi, edi ; eflags ; MACRO ulong2ascii DATASEG IFNDEF FASTMUL cvttab DD 0000000000 ; 0 * 1e9 DD 1000000000 ; 1 * 1e9 DD 2000000000 ; 2 * 1e9 DD 3000000000 ; 3 * 1e9 DD 4000000000 ; 4 * 1e9 ENDIF CODESEG mov ecx,eax ; save original argument mov esi,89705f41h ; 1e-9*2^61 rounded mul esi ; divide by 1e9 by mult. with recip. add eax,80000000h ; round division result mov esi,0abcc7712h ; 2^28/1e8 * 2^30 rounded up adc edx,0 ; EDX<31:29> = argument / 1e9 mov eax,ecx ; restore original argument shr edx,29 ; leading decimal digit, 0...4 mov ecx,8 ; produce eight more digits mov ebx,edx ; flags whether non-zero digit seen yet or edx,'0' ; convert digit to ASCII mov [edi],dl ; store out to memory cmp ebx,1 ; first digit nonzero ? CY=0 : CY=1 sbb edi,-1 ; incr. pointer if first digit non-zero IFDEF FASTMUL imul ebx,1000000000 ; multiply quotient digit by divisor sub eax,ebx ; remainder after first digit ELSE sub eax,[cvttab+ebx*4] ; subtract quotient digit * divisor ENDIF mul esi ; convert number < 1e9 shld edx,eax, 2 ; into fraction such inc edx ; that 1.0 = 2^28 mov eax,edx ; save result shr eax,28 ; next digit and edx,0fffffffh ; fraction part or ebx,eax ; any non-zero yet ? or eax,'0' ; convert digit to ASCII $cvt_loop: mov [edi],al ; store digit out to memory add edx,edx ; 2*fraction cmp ebx,1 ; any non-zero digit seen ? CY=0 : CY=1 lea edx,[edx*4+edx] ; 10*fraction, new digit EAX<31:28>, ; new fraction EAX<27:0> sbb edi,-1 ; incr. ptr if any non-zero digit seen mov eax,edx ; save result shr eax,28 ; next digit = integer part and edx,0fffffffh ; fraction part or ebx,eax ; any non-zero digit yet ? or eax,'0' ; convert digit to ASCII dec ecx ; one more digit jnz $cvt_loop ; until all nine digits done mov [edi],al ; store last digit out to memory mov [byte ptr edi+1],ah ; place string end marker ENDM