ulong2asciiAssembler/80386

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
Gem writer: Norbert Juffa
last updated: 1998-04-18