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