Numeric Conversion

Chapter 9

Numeric Values To Strings

Numeric Values to Hexadecimal Strings

  • Converting a numeric value to a hexadecimal string is relatively straightforward. Just take each nibble (4 bits) in the binary representation and convert that to one of the 16 characters “0” through “9” or “A” through “F”.

; Numeric to hex string functions

          
; btoh-
;
; This procedure converts the binary value
; in the AL register to 2 hexadecimal
; characters and returns those characters
; in the AH (HO hibble) and AL (LO nibble)
; registers. 

btoh        proc

            mov     ah, al    ;Do HO nibble first
            shr     ah, 4     ;Move HO nibble to LO
            or      ah, '0'   ;Convert to char
            cmp     ah, '9'+1 ;Is it 'A'..'F'?
            jb      AHisGood
            
; Convert 3ah..3fh to 'A'..'F'

            add     ah, 7

; Process the LO nibble here
            
AHisGood:   and     al, 0Fh   ;Strip away HO nibble
            or      al, '0'   ;Convert to char
            cmp     al, '9'+1 ;Is it 'A'..'F'?
            jb      ALisGood
            
; Convert 3ah..3fh to 'A'..'F'

            add     al, 7   
ALisGood:   ret
                        
btoh        endp



; btoStr-
;
;  Converts the byte in AL to a string of hexadecimal
; characters and stores them at the buffer pointed at
; by RDI. Buffer must have room for at least 3 bytes.
; This function zero-terminates the string.

btoStr      proc
            push    rax
            call    btoh            ;Do conversion here
            
; Create a zero-terminated string at [RDI] from the
; two characters we converted to hex format:

            mov     [rdi], ah
            mov     [rdi+1], al
            mov     byte ptr [rdi+2], 0
            pop     rax
            ret
btoStr      endp



; wtoStr-
;
;  Converts the word in AX to a string of hexadecimal
; characters and stores them at the buffer pointed at
; by RDI. Buffer must have room for at least 5 bytes.
; This function zero-terminates the string.

wtoStr      proc
            push    rdi
            push    rax     ;Note: leaves LO byte at [rsp]
            
; Use btoStr to convert HO byte to a string:

            mov     al, ah
            call    btoStr

            mov     al, [rsp]       ;Get LO byte
            add     rdi, 2          ;Skip HO chars
            call    btoStr
            
            pop     rax
            pop     rdi
            ret
wtoStr      endp


; dtoStr-
;
;  Converts the dword in EAX to a string of hexadecimal
; characters and stores them at the buffer pointed at
; by RDI. Buffer must have room for at least 9 bytes.
; This function zero-terminates the string.

dtoStr      proc
            push    rdi
            push    rax     ;Note: leaves LO word at [rsp]
            
; Use wtoStr to convert HO word to a string:

            shr     eax, 16
            call    wtoStr

            mov     ax, [rsp]       ;Get LO word
            add     rdi, 4          ;Skip HO chars
            call    wtoStr
            
            pop     rax
            pop     rdi
            ret
dtoStr      endp


; qtoStr-
;
;  Converts the qword in RAX to a string of hexadecimal
; characters and stores them at the buffer pointed at
; by RDI. Buffer must have room for at least 17 bytes.
; This function zero-terminates the string.

hexChar             byte    "0123456789ABCDEF"

qtoStr      proc
            push    rdi
            push    rcx
            push    rdx
            push    rax             ;Leaves LO dword at [rsp]
                            
            lea     rcx, hexChar

            xor     edx, edx        ;Zero extends!
            shld    rdx, rax, 4
            shl     rax, 4
            mov     dl, [rcx][rdx*1] ;Table lookup
            mov     [rdi], dl
                            
; Emit bits 56-59:

            xor     edx, edx
            shld    rdx, rax, 4
            shl     rax, 4
            mov     dl, [rcx][rdx*1]
            mov     [rdi+1], dl
                            
; Emit bits 52-55:

            xor     edx, edx
            shld    rdx, rax, 4
            shl     rax, 4
            mov     dl, [rcx][rdx*1]
            mov     [rdi+2], dl
            
; Emit bits 48-51:

            xor     edx, edx
            shld    rdx, rax, 4
            shl     rax, 4
            mov     dl, [rcx][rdx*1]
            mov     [rdi+3], dl
            
; Emit bits 44-47:

            xor     edx, edx
            shld    rdx, rax, 4
            shl     rax, 4
            mov     dl, [rcx][rdx*1]
            mov     [rdi+4], dl
                            
; Emit bits 40-43:

            xor     edx, edx
            shld    rdx, rax, 4
            shl     rax, 4
            mov     dl, [rcx][rdx*1]
            mov     [rdi+5], dl
            
; Emit bits 36-39:

            xor     edx, edx
            shld    rdx, rax, 4
            shl     rax, 4
            mov     dl, [rcx][rdx*1]
            mov     [rdi+6], dl
            
; Emit bits 32-35:

            xor     edx, edx
            shld    rdx, rax, 4
            shl     rax, 4
            mov     dl, [rcx][rdx*1]
            mov     [rdi+7], dl
            
; Emit bits 28-31:

            xor     edx, edx
            shld    rdx, rax, 4
            shl     rax, 4
            mov     dl, [rcx][rdx*1]
            mov     [rdi+8], dl
            
; Emit bits 24-27:

            xor     edx, edx
            shld    rdx, rax, 4
            shl     rax, 4
            mov     dl, [rcx][rdx*1]
            mov     [rdi+9], dl
            
; Emit bits 20-23:

            xor     edx, edx
            shld    rdx, rax, 4
            shl     rax, 4
            mov     dl, [rcx][rdx*1]
            mov     [rdi+10], dl
            
; Emit bits 16-19:

            xor     edx, edx
            shld    rdx, rax, 4
            shl     rax, 4
            mov     dl, [rcx][rdx*1]
            mov     [rdi+11], dl
            
; Emit bits 12-15:

            xor     edx, edx
            shld    rdx, rax, 4
            shl     rax, 4
            mov     dl, [rcx][rdx*1]
            mov     [rdi+12], dl
            
; Emit bits 8-11:

            xor     edx, edx
            shld    rdx, rax, 4
            shl     rax, 4
            mov     dl, [rcx][rdx*1]
            mov     [rdi+13], dl
            
; Emit bits 4-7:

            xor     edx, edx
            shld    rdx, rax, 4
            shl     rax, 4
            mov     dl, [rcx][rdx*1]
            mov     [rdi+14], dl
            
; Emit bits 0-3:

            xor     edx, edx
            shld    rdx, rax, 4
            shl     rax, 4
            mov     dl, [rcx][rdx*1]
            mov     [rdi+15], dl
            
; Zero-terminate string:

            mov     byte ptr [rdi+16], 0
            
            pop     rax
            pop     rdx
            pop     rcx
            pop     rdi
            ret
qtoStr      endp


; otoStr - 
;
; Converts the oword in RDX:RAX to a string of hexadecimal
; characters and stores them at the buffer pointed at
; by RDI. Buffer must have room for at least 33 bytes.
; This function zero-terminates the string.


otoStr      proc
            push rdi
            push rax ; Note: leaves LO dword at [RSP]

; Use qtoStr to convert each qword to a string:

            mov rax, rdx
            call qtoStr

            mov rax, [rsp] ; Get LO qword
            add rdi, 16 ; Skip HO chars
            call qtoStr

            pop rax
            pop rdi
            ret
otoStr      endp

Unsigned Decimal Values to Strings

  • Decimal output is a little more complicated than hexadecimal output because the HO bits of a binary number affect the LO digits of the decimal representation.

  • It begins by dividing the value by 10 and saving the remainder in a local variable. If the quotient is not 0, the routine recursively calls itself to output any leading digits first. On return from the recursive call, the recursive algorithm outputs the digit associated with the remainder to complete the operation. Here’s how the operation works when printing the decimal value 789:

  1. Divide 789 by 10. The quotient is 78, and the remainder is 9.

  2. Save the remainder (9) in a local variable and recursively call the routine with the quotient.

  3. Recursive entry 1: Divide 78 by 10. The quotient is 7, and the remainder is 8.

  4. Save the remainder (8) in a local variable and recursively call the routine with the quotient.

  5. Recursive entry 2: Divide 7 by 10. The quotient is 0, and the remainder is 7.

  6. Save the remainder (7) in a local variable. Because the quotient is 0, don’t call the routine recursively.

  7. Output the remainder value saved in the local variable (7). Return to the caller (recursive entry 1).

  8. Return to recursive entry 1: Output the remainder value saved in the local variable in recursive entry 1 (8). Return to the caller (original invocation of the procedure).

  9. Original invocation: Output the remainder value saved in the local variable in the original call (9). Return to the original caller of the output routine.

  • Because it uses the div instruction, it can be fairly slow. Fortunately, we can speed it up by using the fist and fbstp instructions.

  • The fbstp instruction converts the 80-bit floating-point value currently sitting on the top of stack to an 18-digit packed BCD value. The fist instruction allows you to load a 64-bit integer onto the FPU stack.

  • So, by using these two instructions, you can (mostly) convert a 64-bit integer to a packed BCD value, which encodes a single decimal digit per 4 bits. Therefore, you can convert the packed BCD result that fbstp produces to a character string by using the same algorithm you use for converting hexadecimal numbers to a string.

; Fast unsigned integer to string function
; using fist/fbstp
            
; utoStr-
;
;  Unsigned integer to string.
;
; Inputs:
;
;    RAX:   Unsigned integer to convert
;    RDI:   Location to hold string.
;
; Note: for 64-bit integers, resulting
; string could be as long as  21 bytes
; (including the zero-terminating byte).

bigNum      qword   1000000000000000000
utoStr      proc
            push    rcx
            push    rdx
            push    rdi
            push    rax
            sub     rsp, 10

; Quick test for zero to handle that special case:

            test    rax, rax
            jnz     not0
            mov     byte ptr [rdi], '0'
            jmp     allDone

; The FBSTP instruction only supports 18 digits.
; 64-bit integers can have up to 19 digits.
; Handle that 19th possible digit here:
            
not0:       cmp     rax, bigNum
            jb      lt19Digits

; The number has 19 digits (which can be 0-9).
; pull off the 19th digit:

            xor     edx, edx
            div     bigNum          ;19th digit in AL
            mov     [rsp+10], rdx   ;Remainder
            or      al, '0'
            mov     [rdi], al
            inc     rdi
            
            
; The number to convert is non-zero.
; Use BCD load and store to convert
; the integer to BCD:

lt19Digits: fild    qword ptr [rsp+10]
            fbstp   tbyte ptr [rsp]
            
            
; Begin by skipping over leading zeros in
; the BCD value (max 19 digits, so the most
; significant digit will be in the LO nibble
; of DH).

            mov     dx, [rsp+8]
            mov     rax, [rsp]
            mov     ecx, 20
            jmp     testFor0
            
Skip0s:     shld    rdx, rax, 4
            shl     rax, 4
testFor0:   dec     ecx         ;Count digits we've processed
            test    dh, 0fh     ;Because the number is not 0
            jz      Skip0s      ;this always terminates
            
; At this point the code has encountered
; the first non-0 digit. Convert the remaining
; digits to a string:

cnvrtStr:   and     dh, 0fh
            or      dh, '0'
            mov     [rdi], dh
            inc     rdi
            mov     dh, 0
            shld    rdx, rax, 4
            shl     rax, 4
            dec     ecx
            jnz     cnvrtStr

; Zero-terminte the string and return:
            
allDone:    mov     byte ptr [rdi], 0
            add     rsp, 10
            pop     rax
            pop     rdi
            pop     rdx
            pop     rcx
            ret
utoStr      endp

Signed Integer Values to Strings

  • To convert a signed integer value to a string, you first check to see if the number is negative; if it is, you emit a hyphen (-) character and negate the value. Then the value can be converted.

; Fast signed integer to string function
; using fist/fbstp
            
; utoStr-
;
;  Unsigned integer to string.
;
; Inputs:
;
;    RAX:   Unsigned integer to convert
;    RDI:   Location to hold string.
;
; Note: for 64-bit integers, resulting
; string could be as long as  20 bytes
; (including the zero-terminating byte).

bigNum      qword   1000000000000000000
utoStr      proc
            push    rcx
            push    rdx
            push    rdi
            push    rax
            sub     rsp, 10

; Quick test for zero to handle that special case:

            test    rax, rax
            jnz     not0
            mov     byte ptr [rdi], '0'
            jmp     allDone

; The FBSTP instruction only supports 18 digits.
; 64-bit integers can have up to 19 digits.
; Handle that 19th possible digit here:
            
not0:       cmp     rax, bigNum
            jb      lt19Digits

; The number has 19 digits (which can be 0-9).
; pull off the 19th digit:

            xor     edx, edx
            div     bigNum          ;19th digit in AL
            mov     [rsp+10], rdx   ;Remainder
            or      al, '0'
            mov     [rdi], al
            inc     rdi
            
            

; The number to convert is non-zero.
; Use BCD load and store to convert
; the integer to BCD:

lt19Digits: fild    qword ptr [rsp+10]
            fbstp   tbyte ptr [rsp]
            
            
; Begin by skipping over leading zeros in
; the BCD value (max 19 digits, so the most
; significant digit will be in the LO nibble
; of DH).

            mov     dx, [rsp+8]
            mov     rax, [rsp]
            mov     ecx, 20
            jmp     testFor0
            
Skip0s:     shld    rdx, rax, 4
            shl     rax, 4
testFor0:   dec     ecx         ;Count digits we've processed
            test    dh, 0fh     ;Because the number is not 0
            jz      Skip0s      ;this always terminates
            
; At this point the code has encountered
; the first non-0 digit. Convert the remaining
; digits to a string:

cnvrtStr:   and     dh, 0fh
            or      dh, '0'
            mov     [rdi], dh
            inc     rdi
            mov     dh, 0
            shld    rdx, rax, 4
            shl     rax, 4
            dec     ecx
            jnz     cnvrtStr

; Zero-terminte the string and return:
            
allDone:    mov     byte ptr [rdi], 0
            add     rsp, 10
            pop     rax
            pop     rdi
            pop     rdx
            pop     rcx
            ret
utoStr      endp



; itoStr - Signed integer to string conversion
;
; Inputs:
;   RAX -   Signed integer to convert
;   RDI -   Destination buffer address

itoStr      proc
            push    rdi
            push    rax
            test    rax, rax
            jns     notNeg
            
; Number was negative, emit '-' and negate
; value.

            mov     byte ptr [rdi], '-'
            inc     rdi
            neg     rax
            
; Call utoStr to convert non-negative number:

notNeg:     call    utoStr
            pop     rax
            pop     rdi
            ret
itoStr      endp

Extended-Precision Unsigned Integers to Strings

; Extended-precision numeric unsigned integer 
; to string function
            
; DivideBy10-
;
;  Divides "divisor" by 10 using fast
;  extended-precision division algorithm
;  that employs the div instruction.
;
;  Returns quotient in "quotient".
;  Returns remainder in rax.
;  Trashes rdx.
;
; RCX - points at oword dividend and location to
;       receive quotient

ten         qword   10

DivideBy10  proc
parm        equ     <[rcx]>

            xor     edx, edx
            mov     rax, parm[8]
            div     ten
            mov     parm[8], rax
            
            mov     rax, parm
            div     ten
            mov     parm, rax
            mov     eax, edx        ;Remainder (always 0..9!)
            ret    
DivideBy10  endp


; Recursive version of otoStr.
; A separate "shell" procedure calls this so that
; this code does not have to preserve all the registers
; it uses (and DivideBy10 uses) on each recursive call.
;
; On entry:
;    Stack contains oword in/out parameter (dividend in/quotient out)
;    RDI- contains location to place output string
;
; Note: this function must clean up stack (parameters)
;       on return.

rcrsvOtoStr proc
value       equ     <[rbp+16]>
remainder   equ     <[rbp-8]>
            push    rbp
            mov     rbp, rsp
            sub     rsp, 8
            lea     rcx, value
            call    DivideBy10
            mov     remainder, al
            
; If the quotient (left in value) is not 0, recursively
; call this routine to output the HO digits.

            mov     rax, value
            or      rax, value[8]
            jz      allDone
            
            mov     rax, value[8]
            push    rax
            mov     rax, value
            push    rax
            call    rcrsvOtoStr

allDone:    mov     al, remainder
            or      al, '0'
            mov     [rdi], al
            inc     rdi
            leave
            ret     16      ;Remove parms from stack
rcrsvOtoStr endp
            

; Nonrecursive shell to the above routine so we don't bother
; saving all the registers on each recursive call.
;
; On entry:
;
;   RDX:RAX- contains oword to print
;   RDI-     buffer to hold string (at least 40 bytes)

otostr      proc

            push    rax
	push	rcx
            push    rdx
            push    rdi

; Special-case zero:

            test    rax, rax
            jnz     not0
            test    rdx, rdx
            jnz     not0
            mov     byte ptr [rdi], '0'
            inc     rdi
            jmp     allDone
            
not0:       push    rdx
            push    rax
            call    rcrsvOtoStr
            
; Zero-terminate string before leaving

allDone:    mov     byte ptr [rdi], 0

            pop     rdi
            pop     rdx
	pop	rcx
            pop     rax
            ret
    
otostr      endp

Extended-Precision Signed Decimal Values to Strings

  • Once you have an extended-precision unsigned decimal output routine, writing an extended-precision signed decimal output routine is easy. The basic algorithm is similar to that for 64-bit integers given earlier:

    1. Check the sign of the number.

    2. If it is positive, call the unsigned output routine to print it. If the number is negative, print a minus sign. Then negate the number and call the unsigned output routine to print it.

; Extended-precision signed integer 
; to string function
            
; DivideBy10-
;
;  Divides "divisor" by 10 using fast
;  extended-precision division algorithm
;  that employs the div instruction.
;
;  Returns quotient in "quotient".
;  Returns remainder in rax.
;  Trashes rdx.
;
; RCX - points at oword dividend and location to
;       receive quotient

ten         qword   10

DivideBy10  proc
parm        equ     <[rcx]>

            xor     edx, edx
            mov     rax, parm[8]
            div     ten
            mov     parm[8], rax
            
            mov     rax, parm
            div     ten
            mov     parm, rax
            mov     eax, edx        ;Remainder (always 0..9!)
            ret    
DivideBy10  endp



; Recursive version of otoStr.
; A separate "shell" procedure calls this so that
; this code does not have to preserve all the registers
; it uses (and DivideBy10 uses) on each recursive call.
;
; On entry:
;    Stack contains oword parameter
;    RDI- contains location to place output string
;
; Note: this function must clean up stack (parameters)
;       on return.

rcrsvOtoStr proc
value       equ     <[rbp+16]>
remainder   equ     <[rbp-8]>
            push    rbp
            mov     rbp, rsp
            sub     rsp, 8
            lea     rcx, value
            call    DivideBy10
            mov     remainder, al
            
; If the quotient (left in value) is not 0, recursively
; call this routine to output the HO digits.

            mov     rax, value
            or      rax, value[8]
            jz      allDone
            
            mov     rax, value[8]
            push    rax
            mov     rax, value
            push    rax
            call    rcrsvOtoStr

allDone:    mov     al, remainder
            or      al, '0'
            mov     [rdi], al
            inc     rdi
            leave
            ret     16      ;Remove parms from stack
rcrsvOtoStr endp
            


; Nonrecursive shell to the above routine so we don't bother
; saving all the registers on each recursive call.
;
; On entry:
;
;   RDX:RAX- contains oword to print
;   RDI-     buffer to hold string (at least 40 bytes)

otostr      proc

            push    rax
            push    rdx
            push    rdi

; Special-case zero:

            test    rax, rax
            jnz     not0
            test    rdx, rdx
            jnz     not0
            mov     byte ptr [rdi], '0'
            inc     rdi
            jmp     allDone
            
not0:       push    rdx
            push    rax
            call    rcrsvOtoStr
            
; Zero-terminate string before leaving

allDone:    mov     byte ptr [rdi], 0

            pop     rdi
            pop     rdx
            pop     rax
            ret
    
otostr      endp
 
 
 
; i128toStr-
;   Converts a 128-bit signed integer to a string
;
; Inputs;
;    RDX:RAX- signed integer to convert
;    RDI-     pointer to buffer to receive string

i128toStr   proc
            push    rax
            push    rdx
            push    rdi
            
            test    rdx, rdx  ;Is number negative?
            jns     notNeg
            
            mov     byte ptr [rdi], '-'
            inc     rdi
            neg     rdx     ;128-bit negation
            neg     rax
            sbb     rdx, 0
            
notNeg:     call    otostr
            pop     rdi
            pop     rdx
            pop     rax
            ret
i128toStr   endp

Formatted Conversions

  • To create nicely formatted tables of values, you will need to write functions that provide appropriate padding in front of the string of digits before actually emitting the digits.

  • The first step is to write iSize and uSize routines that compute the minimum number of character positions needed to display the value. One algorithm to accomplish this is similar to the numeric string conversion routines. In fact, the only difference is that you initialize a counter to 0 upon entry into the routine, and you increment this counter rather than outputting a digit on each recursive call.

  • After the calculation is complete, these routines should return the size of the operand in the EAX register.

; Formatted string output:
            
; utoStr-
;
;  Unsigned integer to string.
;
; Inputs:
;
;    RAX:   Unsigned integer to convert
;    RDI:   Location to hold string.
;
; Note: for 64-bit integers, resulting
; string could be as long as  20 bytes
; (including the zero-terminating byte).

bigNum      qword   1000000000000000000
utoStr      proc
            push    rcx
            push    rdx
            push    rdi
            push    rax
            sub     rsp, 10

; Quick test for zero to handle that special case:

            test    rax, rax
            jnz     not0
            mov     byte ptr [rdi], '0'
            jmp     allDone

; The FBSTP instruction only supports 18 digits.
; 64-bit integers can have up to 19 digits.
; Handle that 19th possible digit here:
            
not0:       cmp     rax, bigNum
            jb      lt19Digits

; The number has 19 digits (which can be 0-9).
; pull off the 19th digit:

            xor     edx, edx
            div     bigNum          ;19th digit in AL
            mov     [rsp+10], rdx   ;Remainder
            or      al, '0'
            mov     [rdi], al
            inc     rdi
            
            

; The number to convert is non-zero.
; Use BCD load and store to convert
; the integer to BCD:

lt19Digits: fild    qword ptr [rsp+10]
            fbstp   tbyte ptr [rsp]
            
            
; Begin by skipping over leading zeros in
; the BCD value (max 19 digits, so the most
; significant digit will be in the LO nibble
; of DH).

            mov     dx, [rsp+8]
            mov     rax, [rsp]
            mov     ecx, 20
            jmp     testFor0
            
Skip0s:     shld    rdx, rax, 4
            shl     rax, 4
testFor0:   dec     ecx         ;Count digits we've processed
            test    dh, 0fh     ;Because the number is not 0
            jz      Skip0s      ;this always terminates
            
; At this point the code has encountered
; the first non-0 digit. Convert the remaining
; digits to a string:

cnvrtStr:   and     dh, 0fh
            or      dh, '0'
            mov     [rdi], dh
            inc     rdi
            mov     dh, 0
            shld    rdx, rax, 4
            shl     rax, 4
            dec     ecx
            jnz     cnvrtStr

; Zero-terminte the string and return:
            
allDone:    mov     byte ptr [rdi], 0
            add     rsp, 10
            pop     rax
            pop     rdi
            pop     rdx
            pop     rcx
            ret
utoStr      endp



; itoStr - Signed integer to string conversion
;
; Inputs:
;   RAX -   Signed integer to convert
;   RDI -   Destination buffer address

itoStr      proc
            push    rdi
            push    rax
            test    rax, rax
            jns     notNeg
            
; Number was negative, emit '-' and negate
; value.

            mov     byte ptr [rdi], '-'
            inc     rdi
            neg     rax
            
; Call utoStr to convert non-negative number:

notNeg:     call    utoStr
            pop     rax
            pop     rdi
            ret
itoStr      endp


; uSize-
;  Determines how many character positions it will take
; to hold a 64-bit numeric-to-string conversion.
; VERY brute-force algorithm. Just compares the value
; in RAX against 18 powers of 10 to determine if there
; are 1-19 digits in the number. 
;
; Input
;   RAX-    Number to check
;
; Returns-
;   RAX-    Number of character positions required.

dig2        qword   10
dig3        qword   100
dig4        qword   1000
dig5        qword   10000
dig6        qword   100000
dig7        qword   1000000
dig8        qword   10000000
dig9        qword   100000000
dig10       qword   1000000000
dig11       qword   10000000000
dig12       qword   100000000000
dig13       qword   1000000000000
dig14       qword   10000000000000
dig15       qword   100000000000000
dig16       qword   1000000000000000
dig17       qword   10000000000000000
dig18       qword   100000000000000000
dig19       qword   1000000000000000000

uSize       proc
            push    rdx
            cmp     rax, dig10
            jae     ge10
            cmp     rax, dig5
            jae     ge5
            mov     edx, 4
            cmp     rax, dig4
            jae     allDone
            dec     edx
            cmp     rax, dig3
            jae     allDone
            dec     edx
            cmp     rax, dig2
            jae     allDone
            dec     edx
            jmp     allDone
            
ge5:        mov     edx, 9
            cmp     rax, dig9
            jae     allDone
            dec     edx
            cmp     rax, dig8
            jae     allDone
            dec     edx
            cmp     rax, dig7
            jae     allDone
            dec     edx
            cmp     rax, dig6
            jae     allDone
            dec     edx     ;Must be 5
            jmp     allDone
            
            
ge10:       cmp     rax, dig14
            jae     ge14
            mov     edx, 13
            cmp     rax, dig13
            jae     allDone
            dec     edx
            cmp     rax, dig12
            jae     allDone
            dec     edx
            cmp     rax, dig11
            jae     allDone
            dec     edx     ;Must be 10
            jmp     allDone
            
ge14:       mov     edx, 19
            cmp     rax, dig19
            jae     allDone
            dec     edx
            cmp     rax, dig18
            jae     allDone
            dec     edx
            cmp     rax, dig17
            jae     allDone
            dec     edx
            cmp     rax, dig16
            jae     allDone
            dec     edx
            cmp     rax, dig15
            jae     allDone
            dec     edx     ;Must be 14             


allDone:    mov     rax, rdx        ;Return digit count
            pop     rdx
            ret
uSize       endp


; iSize-
;  Determines the number of print positions required by
; a 64-bit signed integer.

iSize       proc
            test    rax, rax
            js      isNeg

            jmp     uSize   ; Effectively a call and ret

; If the number is negative, negate it, call uSize,
; and then bump the size up by 1 (for the '-' character)
            
isNeg:      neg     rax
            call    uSize
            inc     rax
            ret
iSize       endp


; utoStrSize-
;   Converts an unsigned integer to a formatted string
; having at least "minDigits" character positions.
; If the actual number of digits is smaller than
; "minDigits" then this procedure inserts encough
; "pad" characters to extend the size of the string.
;
; Inputs:
;   RAX -   Number to convert to string
;   CL-     minDigits (minimum print positions)
;   CH-     Padding character
;   RDI -   Buffer pointer for output string

utoStrSize  proc
            push    rcx
            push    rdi
            push    rax
            
            call    uSize   ;Get actual number of digits
            sub     cl, al  ;>= the minimum size?
            jbe     justConvert
            
; If the minimum size is greater than the number of actual
; digits, we need to emit padding characters here.
;
; Note that this code used "sub" rather than "cmp" above.
; As a result, CL now contains the number of padding
; characters to emit to the string (CL is always positive
; at this point, as negative and zero results would have
; branched to justConvert).

padLoop:    mov     [rdi], ch
            inc     rdi
            dec     cl
            jne     padLoop

; Okay, any necessary padding characters have already been
; added to the string. Call utostr to convert the number
; to a string and append to the buffer:

justConvert:
            mov     rax, [rsp]      ;Retrieve original value
            call    utoStr
            
            pop     rax
            pop     rdi
            pop     rcx
            ret
utoStrSize  endp    

; itoStrSize-
;   Converts a signed integer to a formatted string
; having at least "minDigits" character positions.
; If the actual number of digits is smaller than
; "minDigits" then this procedure inserts encough
; "pad" characters to extend the size of the string.
;
; Inputs:
;   RAX -   Number to convert to string
;   CL-     minDigits (minimum print positions)
;   CH-     Padding character
;   RDI -   Buffer pointer for output string

itoStrSize  proc
            push    rcx
            push    rdi
            push    rax
            
            call    iSize   ;Get actual number of digits
            sub     cl, al  ;>= the minimum size?
            jbe     justConvert
            
; If the minimum size is greater than the number of actual
; digits, we need to emit padding characters here.
;
; Note that this code used "sub" rather than "cmp" above.
; As a result, CL now contains the number of padding
; characters to emit to the string (CL is always positive
; at this point, as negative and zero results would have
; branched to justConvert).

padLoop:    mov     [rdi], ch
            inc     rdi
            dec     cl
            jne     padLoop

; Okay, any necessary padding characters have already been
; added to the string. Call utostr to convert the number
; to a string and append to the buffer:

justConvert:
            mov     rax, [rsp]      ;Retrieve original value
            call    itoStr
            
            pop     rax
            pop     rdi
            pop     rcx
            ret
itoStrSize  endp

Floating-Point Values to Strings

  • Floating-point values can be converted to strings in one of two forms:

  1. Decimal notation conversion (for example, ± xxx.yyy format)

  2. Exponential (or scientific) notation conversion (for example, ± x.yyyyye ± zz format)

  • By using the x87 FPU for all floating-point arithmetic during the conversion, all we need to do is write code to convert real10 values into string form.

  • To output the mantissa in decimal form with approximately 18 digits of precision, the trick is to successively multiply or divide the floating-point value by 10 until the number is between 1e+18 and just less than 1e+19.

  • Once the exponent is in the appropriate range, the mantissa bits form an 18-digit integer value (no fractional part), which can be converted to a decimal string to obtain the 18 digits that make up the mantissa value.

Floating-Point Value to a Decimal String

; Floating-point to string conversion


; TenTo17 - Holds the value 1.0e+17. Used to get a floating 
;           point number to the range x.xxxxxxxxxxxxe+17

TenTo17     real10  1.0e+17
            
; PotTblN- Hold powers of ten raised to negative powers of two.
            
PotTblN     real10  1.0,
                    1.0e-1,
                    1.0e-2,
                    1.0e-4,
                    1.0e-8,
                    1.0e-16,
                    1.0e-32,
                    1.0e-64,
                    1.0e-128,
                    1.0e-256,
                    1.0e-512,
                    1.0e-1024,
                    1.0e-2048,
                    1.0e-4096                               
                                    
                                            
; PotTblP- Hold powers of ten raised to positive powers of two.
            
            align   4
PotTblP     real10  1.0,
                    1.0e+1,
                    1.0e+2,
                    1.0e+4,
                    1.0e+8,
                    1.0e+16,
                    1.0e+32,
                    1.0e+64,
                    1.0e+128,
                    1.0e+256,
                    1.0e+512,
                    1.0e+1024,
                    1.0e+2048,
                    1.0e+4096
                                    
; ExpTbl- Integer equivalents to the powers in the tables 
;         above.
            
            align   4
ExpTab      dword   0,
                    1,
                    2,
                    4,
                    8,
                    16,
                    32,
                    64,
                    128,
                    256,
                    512,
                    1024,
                    2048,
                    4096
            
            
            .code
            
print       proc
            push    rax
            push    rbx
            push    rcx
            push    rdx
            push    r8
            push    r9
            push    r10
            push    r11
            
            push    rbp
            mov     rbp, rsp
            sub     rsp, 40
            and     rsp, -16
            
            mov     rcx, [rbp+72]   ;Return address
            call    printf
            
            mov     rcx, [rbp+72]
            dec     rcx
skipTo0:    inc     rcx
            cmp     byte ptr [rcx], 0
            jne     skipTo0
            inc     rcx
            mov     [rbp+72], rcx
            
            leave
            pop     r11
            pop     r10
            pop     r9
            pop     r8
            pop     rdx
            pop     rcx
            pop     rbx
            pop     rax
            ret
print       endp
            


;*************************************************************
;
; expToBuf-
;
;  Unsigned integer to buffer.
; Used to output up to 4-digit exponents.
;
; Inputs:
;
;    EAX:   Unsigned integer to convert
;    ECX:   Print width 1-4
;    RDI:   Points at buffer.
;

expToBuf    proc

expWidth    equ     <[rbp+16]>
exp         equ     <[rbp+8]>
bcd         equ     <[rbp-16]>

            push    rdx
            push    rcx     ;At [RBP+16]
            push    rax     ;At [RBP+8]
            push    rbp
            mov     rbp, rsp
            sub     rsp, 16

; Verify exponent digit count is in the range 1-4:

            cmp     rcx, 1
            jb      badExp
            cmp     rcx, 4
            ja      badExp
            mov     rdx, rcx
            
; Verify the actual exponent will fit in the number of digits:

            cmp     rcx, 2
            jb      oneDigit
            je      twoDigits
            cmp     rcx, 3
            ja      fillZeros       ;4 digits, no error
            cmp     eax, 1000
            jae     badExp
            jmp     fillZeros
            
oneDigit:   cmp     eax, 10
            jae     badExp
            jmp     fillZeros
            
twoDigits:  cmp     eax, 100
            jae     badExp
            
            
; Fill in zeros for exponent:

fillZeros:  mov     byte ptr [rdi+rcx*1-1], '0'
            dec     ecx
            jnz     fillZeros
            
; Point RDI at the end of the buffer:

            lea     rdi, [rdi+rdx*1-1]
            mov     byte ptr [rdi+1], 0
            push    rdi                 ;Save pointer to end

; Quick test for zero to handle that special case:

            test    eax, eax
            jz      allDone
            
; The number to convert is non-zero.
; Use BCD load and store to convert
; the integer to BCD:

            fild    dword ptr exp   ;Get integer value
            fbstp   tbyte ptr bcd   ;Convert to BCD
            
; Begin by skipping over leading zeros in
; the BCD value (max 10 digits, so the most
; significant digit will be in the HO nibble
; of byte 4).

            mov     eax, bcd        ;Get exponent digits
            mov     ecx, expWidth   ;Number of total digits
            
OutputExp:  mov     dl, al
            and     dl, 0fh
            or      dl, '0'
            mov     [rdi], dl
            dec     rdi
            shr     ax, 4
            jnz     OutputExp


; Zero-terminte the string and return:
            
allDone:    pop     rdi
            leave
            pop     rax
            pop     rcx
            pop     rdx
            clc
            ret
            
badExp:     leave
            pop     rax
            pop     rcx
            pop     rdx
            stc
            ret
            
expToBuf    endp


;*************************************************************
;                                                                  
; FPDigits-                                                        
;                                                                  
; Used to convert a floating point number on the FPU 
; stack (ST(0)) to a string of digits.                                           
;                                                                  
; Entry Conditions:                                                
;                                                                  
; ST(0)-    80-bit number to convert.                              
; RDI-      Points at array of at least 18 bytes where FPDigits    
;           stores the output string.                      
;                                                                  
; Exit Conditions:                                                 
;                                                                  
; RDI-      Converted digits are found here.                       
; RAX-      Contains exponent of the number.                       
; CL-       Contains the sign of the mantissa (" " or "-").
;                                                                  
;*************************************************************

P10TblN     equ     <real10 ptr [r8]>
P10TblP     equ     <real10 ptr [r9]>
xTab        equ     <dword ptr [r10]>

FPDigits    proc
            push    rbx
            push    rdx
            push    rsi
            push    r8
            push    r9
            push    r10

; Special case if the number is zero.

            
            ftst
            fstsw   ax
            sahf
            jnz     fpdNotZero

; The number is zero, output it as a special case.

            fstp    tbyte ptr [rdi]  ;Pop value off FPU stack.
            mov     rax, "00000000"
            mov     [rdi], rax 
            mov     [rdi+8], rax 
            mov     [rdi+16], ax
            add     rdi, 18 
            xor     edx, edx         ; Return an exponent of 0.
            mov     bl, ' '          ; Sign is positive.
            jmp     fpdDone
                    
fpdNotZero:
            
; If the number is not zero, then fix the sign of the value.
            
            mov     bl, ' '      ; Assume it's positive.
            jnc     WasPositive  ; Flags set from sahf above.
            
            fabs             ; Deal only with positive numbers.
            mov     bl, '-'  ; Set the sign return result.
                    
WasPositive:

; Get the number between one and ten so we can figure out 
; what the exponent is.  Begin by checking to see if we have 
; a positive or negative exponent.
            
            
            xor     edx, edx        ; Initialize exponent to 0.
            fld1
            fcomip  st(0), st(1)
            jbe     PosExp

; We've got a value between zero and one, exclusive, 
; at this point.  That means this number has a negative 
; exponent.  Multiply the number by an appropriate power
; of ten until we get it in the range 1..10.
                    
            mov     esi, sizeof PotTblN     ; After last element.
            mov     ecx, sizeof ExpTab      ; Ditto.
            lea     r8, PotTblN
            lea     r9, PotTblP
            lea     r10, ExpTab

CmpNegExp:
            sub     esi, 10          ; Move to previous element.
            sub     ecx, 4           ; Zeros HO bytes
            jz      test1

            fld     P10TblN[ rsi*1 ] ; Get current power of 10.
            fcomip  st(0), st(1)     ; Compare against NOS.
            jbe     CmpNegExp        ;While Table >= value.

            mov     eax, xTab[ rcx*1 ]
            test    eax, eax
            jz      didAllDigits

            sub     edx, eax
            fld     P10TblP[ rsi*1 ]
            fmulp
            jmp     CmpNegExp

; If the remainder is *exactly* 1.0, then we can branch
; on to InRange1_10, otherwise, we still have to multiply
; by 10.0 because we've overshot the mark a bit.

test1:
            fld1
            fcomip  st(0), st(1)
            je      InRange1_10

didAllDigits:
                            
; If we get to this point, then we've indexed through
; all the elements in the PotTblN and it's time to stop.

            fld     P10TblP[ 10 ]   ; 10.0
            fmulp
            dec     edx
            jmp     InRange1_10
                    
                    
                    
            
;  At this point, we've got a number that is one or greater.
;  Once again, our task is to get the value between 1 and 10.
            
PosExp:
            
            mov     esi, sizeof PotTblP ; After last element.
            mov     ecx, sizeof ExpTab  ; Ditto.
            lea     r9, PotTblP
            lea     r10, ExpTab

CmpPosExp:
            sub     esi, 10         ; Move back 1 element in
            sub     ecx, 4          ;  PotTblP and ExpTbl.
            fld     P10TblP[ rsi*1 ]
            fcomip  st(0), st(1)
            ja      CmpPosExp;
            mov     eax, xTab[ rcx*1 ]
            test    eax, eax
            jz      InRange1_10
            
            add     edx, eax
            fld     P10TblP[ rsi*1 ]
            fdivp
            jmp     CmpPosExp
                    

                            
InRange1_10:

; Okay, at this point the number is in the range 1 <= x < 10,
; Let's multiply it by 1e+18 to put the most significant digit
; into the 18th print position.  Then convert the result to
; a BCD value and store away in memory.

            
            sub     rsp, 24         ; Make room for BCD result.
            fld     TenTo17
            fmulp
            
; We need to check the floating-point result to make sure it
; is not outside the range we can legally convert to a BCD 
; value.
;
; Illegal values will be in the range:
;
;  >999,999,999,999,999,999 ..<1,000,000,000,000,000,000
;      $403a_de0b_6b3a_763f_ff01..$403a_de0b_6b3a_763f_ffff
;
; Should one of these values appear, round the result up to
;
;           $403a_de0b_6b3a_7640_0000
            
            fstp    real10 ptr [rsp]
            cmp     word ptr [rsp+8], 403ah
            jne     noRounding
            
            cmp     dword ptr [rsp+4], 0de0b6b3ah
            jne     noRounding
                    
            mov     eax, [rsp]
            cmp     eax, 763fff01h
            jb      noRounding;
            cmp     eax, 76400000h
            jae     TooBig
                            
            fld     TenTo17
            inc     edx     ;Inc exp as this is really 10^18. 
            jmp     didRound
                                    
; If we get down here, there was some problems getting the
; value in the range 1 <= x <= 10 above and we've got a value
; that is 10e+18 or slightly larger. We need to compensate for
; that here.

TooBig:
            lea     r9, PotTblP
            fld     real10 ptr [rsp]
            fld     P10TblP[ 10 ]   ; /10
            fdivp
            inc     edx             ; Adjust exp due to fdiv.
            jmp     didRound
                            
                                    
noRounding:
            fld     real10 ptr [rsp]
didRound:   
            fbstp   tbyte ptr [rsp]

            
            
; The data on the stack contains 18 BCD digits.  Convert these
; to ASCII characters and store them at the destination location
; pointed at by edi.
            
            mov     ecx, 8
repeatLp:
            mov     al, byte ptr [rsp+rcx]
            shr     al, 4                   ;Always in the
            or      al, '0'                 ; range 0..9
            mov     [rdi], al
            inc     rdi
            
            mov     al, byte ptr [rsp+rcx]
            and     al, 0fh
            or      al, '0'
            mov     [rdi], al
            inc     rdi
            
            dec     ecx     
            jns     repeatLp

            add     rsp, 24         ; Remove BCD data from stack.

fpdDone:

            mov     eax, edx        ; Return exponent in EAX.
            mov     cl, bl          ; Return sign in CL
            pop     r10
            pop     r9
            pop     r8
            pop     rsi
            pop     rdx
            pop     rbx
            ret
                    
                    
FPDigits    endp

      
      
      
      
;***********************************************************
;                                                           
; r10ToStr-                                                 
;                                                           
; Converts a REAL10 floating-point number to the       
; corresponding string of digits.  Note that this           
; function always emits the string using decimal            
; notation.  For scientific notation, use the e10ToBuf      
; routine.                                                  
;                                                           
; On Entry:                                                 
;                                                           
; r10-              Real10 value to convert.
;                   Passed in ST(0).                       
;                                                           
; fWidth-           Field width for the number (note that this   
;                   is an *exact* field width, not a minimum         
;                   field width).
;                   Passed in EAX (RAX).                                    
;                                                           
; decimalpts-       # of digits to display after the decimal pt.
;                   Passed in EDX (RDX). 
;                                                           
; fill-             Padding character if the number is smaller   
;                   than the specified field width.
;                   Passed in CL (RCX).                  
;                                                                            
; buffer-           r10ToStr stores the resulting characters in  
;                   this string.
;                   Address passed in RDI.
;
; maxLength-        Maximum string length.
;                   Passed in R8d (R8).                                     
;                                                                            
; On Exit:                                                  
;                                                           
; Buffer contains the newly formatted string.  If the       
; formatted value does not fit in the width specified,      
; r10ToStr will store "#" characters into this string.      
;                                                           
; Carry-    Clear if success, set if an exception occurs.                                                         
;           If width is larger than the maximum length of          
;           the string specified by buffer, this routine        
;           will return with the carry set and RAX=-1.                                             
;                                                           
;***********************************************************


r10ToStr    proc


; Local variables:

fWidth      equ     <dword ptr [rbp-8]>     ;RAX: uns32
decDigits   equ     <dword ptr [rbp-16]>    ;RDX: uns32
fill        equ     <[rbp-24]>              ;CL: char
bufPtr      equ     <[rbp-32]>              ;RDI: pointer
exponent    equ     <dword ptr [rbp-40]>    ;uns32
sign        equ     <byte ptr [rbp-48]>     ;char
digits      equ     <byte ptr [rbp-128]>    ;char[80]

            push    rdi
            push    rbx
            push    rcx
            push    rdx
            push    rsi
            push    rax
            push    rbp
            mov     rbp, rsp
            sub     rsp, 128        ;128 bytes of local vars

; First, make sure the number will fit into the 
; specified string.
            
            cmp     eax, r8d 
            jae     strOverflow
            
            test    eax, eax
            jz      voor

            mov     bufPtr, rdi
            mov     qword ptr decDigits, rdx
            mov     fill, rcx
            mov     qword ptr fWidth, rax
            
;  If the width is zero, raise an exception:

            test    eax, eax
            jz      voor
            
; If the width is too big, raise an exception:

            cmp     eax, fWidth
            ja      badWidth        

; Okay, do the conversion. 
; Begin by processing the mantissa digits:
                    
            lea     rdi, digits     ; Store result here.
            call    FPDigits        ; Convert r80 to string.
            mov     exponent, eax   ; Save away exp result.
            mov     sign, cl        ; Save mantissa sign char.

; Round the string of digits to the number of significant 
; digits we want to display for this number:

            cmp     eax, 17
            jl      dontForceWidthZero

            xor     rax, rax        ; If the exp is negative, or
                                    ; too large, set width to 0.
dontForceWidthZero:
            mov     rbx, rax        ; Really just 8 bits.
            add     ebx, decDigits  ; Compute rounding position.
            cmp     ebx, 17
            jge     dontRound       ; Don't bother if a big #.


; To round the value to the number of significant digits,
; go to the digit just beyond the last one we are considering
; (eax currently contains the number of decimal positions)
; and add 5 to that digit.  Propagate any overflow into the
; remaining digit positions.
                    
                    
            inc     ebx           ; Index+1 of last sig digit.
            mov     al, digits[rbx*1] ; Get that digit.
            add     al, 5             ; Round (e.g., +0.5 ).
            cmp     al, '9'
            jbe     dontRound

            mov     digits[ rbx*1 ], '0'+10 ; Force to zero
whileDigitGT9:                              ; (see sub 10 below).
            sub     digits[ rbx*1 ], 10     ; Sub out overflow, 
            dec     ebx                     ; carry, into prev
            js      hitFirstDigit;          ; digit (until 1st
                                            ;  digit in the #).
            inc     digits[rbx*1]
            cmp     digits[rbx], '9'        ; Overflow if > '9'.
            ja      whileDigitGT9
            jmp     dontRound
            
                                    
hitFirstDigit:
                                            
; If we get to this point, then we've hit the first
; digit in the number.  So we've got to shift all
; the characters down one position in the string of
; bytes and put a "1" in the first character position.
            
            mov     ebx, 17
repeatUntilEBXeq0:
            
            mov     al, digits[rbx*1]
            mov     digits[rbx*1+1], al
            dec     ebx     
            jnz     repeatUntilEBXeq0
                    
            mov     digits, '1'
            inc     exponent    ; Because we added a digit.
                    
dontRound: 
                    
            
; Handle positive and negative exponents separately.

            mov     rdi, bufPtr ; Store the output here.
            cmp     exponent, 0
            jge     positiveExponent

; Negative exponents:
; Handle values between 0 & 1.0 here (negative exponents
; imply negative powers of ten).
;
; Compute the number's width.  Since this value is between
; 0 & 1, the width calculation is easy: it's just the
; number of decimal positions they've specified plus three
; (since we need to allow room for a leading "-0.").
                    
            
            mov     ecx, decDigits
            add     ecx, 3
            cmp     ecx, 4
            jae     minimumWidthIs4

            mov     ecx, 4  ; Minimum possible width is four.

minimumWidthIs4:
            cmp     ecx, fWidth
            ja      widthTooBig 
            
; This number will fit in the specified field width,
; so output any necessary leading pad characters.
            
            mov     al, fill
            mov     edx, fWidth
            sub     edx, ecx
            jmp     testWhileECXltWidth
            
            
whileECXltWidth:
            mov     [rdi], al
            inc     rdi
            inc     ecx
testWhileECXltWidth:
            cmp     ecx, fWidth
            jb      whileECXltWidth
                            
; Output " 0." or "-0.", depending on the sign of the number.
            
            mov     al, sign
            cmp     al, '-'
            je      isMinus
                    
            mov     al, ' '
            
isMinus:    mov     [rdi], al
            inc     rdi
            inc     edx
                    
            mov     word ptr [rdi], '.0'
            add     rdi, 2
            add     edx, 2
            
; Now output the digits after the decimal point:

            xor     ecx, ecx        ; Count the digits in ecx.
            lea     rbx, digits     ; Pointer to data to output.
                                    
; If the exponent is currently negative, or if
; we've output more than 18 significant digits,
; just output a zero character.
            
repeatUntilEDXgeWidth: 
            mov     al, '0'
            inc     exponent
            js      noMoreOutput
            
            cmp     ecx, 18
            jge     noMoreOutput
            
            mov     al, [rbx]
            inc     ebx
                    
noMoreOutput:
            mov     [rdi], al
            inc     rdi
            inc     ecx
            inc     edx
            cmp     edx, fWidth
            jb      repeatUntilEDXgeWidth
            jmp     r10BufDone


; If the number's actual width was bigger than the width
; specified by the caller, emit a sequence of '#' characters
; to denote the error.
                            
widthTooBig:
            

; The number won't fit in the specified field width,
; so fill the string with the "#" character to indicate
; an error.
                    
                    
            mov     ecx, fWidth
            mov     al, '#'
fillPound:  mov     [rdi], al
            inc     rdi
            dec     ecx
            jnz     fillPound
            jmp     r10BufDone

            
; Handle numbers with a positive exponent here.

positiveExponent:
            
; Compute # of digits to the left of the ".".
; This is given by:
;
;                   Exponent     ; # of digits to left of "."
;           +       2            ; Allow for sign and there
;                                ; is always 1 digit left of "."
;           +       decimalpts   ; Add in digits right of "."
;           +       1            ; If there is a decimal point.
            

            mov     edx, exponent   ; Digits to left of "."
            add     edx, 2          ; 1 digit + sign posn.
            cmp     decDigits, 0
            je      decPtsIs0

            add     edx, decDigits  ; Digits to right of "."
            inc     edx             ; Make room for the "."
            
decPtsIs0:
            
; Make sure the result will fit in the
; specified field width.
            
            cmp     edx, fWidth
            ja      widthTooBig
            
                    
; If the actual number of print positions
; is less than the specified field width,
; output leading pad characters here.
            
            cmp     edx, fWidth
            jae     noFillChars
            
            mov     ecx, fWidth
            sub     ecx, edx
            jz      noFillChars
            mov     al, fill
fillChars:  mov     [rdi], al
            inc     rdi
            dec     ecx
            jnz     fillChars
                    
noFillChars:
            
            
; Output the sign character.
            
            mov     al, sign
            cmp     al, '-'
            je      outputMinus;
            
            mov     al, ' '
                    
outputMinus:
            mov     [rdi], al
            inc     rdi
                    
; Okay, output the digits for the number here.
            
            
            xor     ecx, ecx        ; Counts  # of output chars.
            lea     rbx, digits     ; Ptr to digits to output.
            
            
; Calculate the number of digits to output
; before and after the decimal point.
            
            
            mov     edx, decDigits  ; Chars after "."
            add     edx, exponent   ; # chars before "."
            inc     edx             ; Always one digit before "."
            
                    
; If we've output fewer than 18 digits, go ahead
; and output the next digit.  Beyond 18 digits,
; output zeros.
            
repeatUntilEDXeq0:
            mov     al, '0'
            cmp     ecx, 18
            jnb     putChar
            
            mov     al, [rbx]
            inc     rbx

putChar:    mov     [rdi], al
            inc     rdi     
            
; If the exponent decrements down to zero,
; then output a decimal point.
            
            cmp     exponent, 0
            jne     noDecimalPt
            cmp     decDigits, 0
            je      noDecimalPt
            
            mov     al, '.'
            mov     [rdi], al
            inc     rdi
                    
noDecimalPt:
            dec     exponent      ; Count down to "." output.
            inc     ecx           ; # of digits thus far.
            dec     edx           ; Total # of digits to output.
            jnz     repeatUntilEDXeq0
                    

; Zero-terminate string and leave:
            
r10BufDone: mov     byte ptr [rdi], 0
            leave
            clc     ;No error
            jmp     popRet

badWidth:   mov     rax, -2 ;Illegal character
            jmp     ErrorExit
            
strOverflow:
            mov     rax, -3 ;String overflow
            jmp     ErrorExit

voor:       or      rax, -1 ;Range error
ErrorExit:  leave
            stc     ;Error
            mov     [rsp], rax      ;Change RAX on return
            
popRet:     pop     rax
            pop     rsi
            pop     rdx
            pop     rcx
            pop     rbx
            pop     rdi
            ret

r10ToStr    endp



;***********************************************************
;                                                           
; eToStr-                                                   
;                                                           
; Converts a REAL10 floating-point number to the       
; corresponding string of digits.  Note that this           
; function always emits the string using scientific                  
; notation, use the r10ToStr routine for decimal notation.   
;                                                           
; On Entry:                                                 
;                                                           
;    e10-           Real10 value to convert.
;                   Passed in ST(0)                     
;                                                           
;    width-         Field width for the number (note that this   
;                   is an *exact* field width, not a minimum     
;                   field width).
;                   Passed in RAX (LO 32 bits).                                
;                                                           
;    fill-          Padding character if the number is smaller   
;                   than the specified field width.
;                   Passed in RCX.                  
;                                                                            
;    buffer-        e10ToStr stores the resulting characters in  
;                   this buffer (passed in EDI).
;                   Passed in RDI (LO 32 bits).                 
;                                                           
;    expDigs-       Number of exponent digits (2 for real4,     
;                   3 for real8, and 4 for real10).
;                   Passed in RDX (LO 8 bits)             
;                                                                            
;
;    maxLength-     Maximum buffer size.
;                   Passed in R8.                                     
; On Exit:                                                  
;                                                           
;    Buffer contains the newly formatted string.  If the    
;    formatted value does not fit in the width specified,   
;    e10ToStr will store "#" characters into this string.   
;                                                           
;-----------------------------------------------------------
;                                                           
; Unlike the integer to string conversions, this routine    
; always right justifies the number in the specified        
; string.  Width must be a positive number, negative        
; values are illegal (actually, they are treated as         
; *really* big positive numbers which will always raise     
; a string overflow exception.                              
;                                                           
;***********************************************************

e10ToStr    proc

fWidth      equ     <[rbp-8]>       ;RAX
buffer      equ     <[rbp-16]>      ;RDI
expDigs     equ     <[rbp-24]>      ;RDX
rbxSave     equ     <[rbp-32]>
rcxSave     equ     <[rbp-40]>
rsiSave     equ     <[rbp-48]>
Exponent    equ     <dword ptr [rbp-52]>
MantSize    equ     <dword ptr [rbp-56]>
Sign        equ     <byte ptr [rbp-60]>
Digits      equ     <byte ptr [rbp-128]>

            push    rbp
            mov     rbp, rsp
            sub     rsp, 128
	
            cmp     eax, r8d
            jae     strOvfl
            mov     byte ptr [rdi+rax*1], 0 ; 0-terminate str
	
            
            mov     buffer, rdi
            mov     rsiSave, rsi
            mov     rcxSave, rcx
            mov     rbxSave, rbx
            mov     fWidth, rax
            mov     expDigs, rdx
            
; First, make sure the width isn't zero.
            
            test    eax, eax
            jz      voor

; Just to be on the safe side, don't allow widths greater 
; than 1024:

            cmp     eax, 1024
            ja      badWidth

; Okay, do the conversion.

            lea     rdi, Digits     ; Store result string here.
            call    FPDigits        ; Convert e80 to digit str.
            mov     Exponent, eax   ; Save away exponent result.
            mov     Sign, cl        ; Save mantissa sign char.

; Verify that there is sufficient room for the mantissa's sign,
; the decimal point, two mantissa digits, the "E", 
; and the exponent's sign.  Also add in the number of digits
; required by the exponent (2 for real4, 3 for real8, 4 for 
; real10).
;
; -1.2e+00    :real4
; -1.2e+000   :real8
; -1.2e+0000  :real10
            
            
            mov     ecx, 6          ; Char posns for above chars.
            add     ecx, expDigs    ; # of digits for the exp.
            cmp     ecx, fWidth
            jbe     goodWidth
            
; Output a sequence of "#...#" chars (to the specified width)
; if the width value is not large enough to hold the 
; conversion:

            mov     ecx, fWidth
            mov     al, '#'
            mov     rdi, buffer
fillPound:  mov     [rdi], al
            inc     rdi
            dec     ecx
            jnz     fillPound
            jmp     exit_eToBuf


; Okay, the width is sufficient to hold the number, do the
; conversion and output the string here:

goodWidth:
            
            mov     ebx, fWidth     ; Compute the # of mantissa
            sub     ebx, ecx        ; digits to display.
            add     ebx, 2          ; ECX allows for 2 mant digs.
            mov     MantSize,ebx
                    
            
; Round the number to the specified number of print positions.
; (Note: since there are a maximum of 18 significant digits,
;  don't bother with the rounding if the field width is greater
;  than 18 digits.)
            
            
            cmp     ebx, 18
            jae     noNeedToRound
                    
; To round the value to the number of significant digits,
; go to the digit just beyond the last one we are considering
; (ebx currently contains the number of decimal positions)
; and add 5 to that digit.  Propagate any overflow into the
; remaining digit positions.
            
            mov     al, Digits[rbx*1] ; Get least sig digit + 1.
            add     al, 5             ; Round (e.g., +0.5 ).
            cmp     al, '9'
            jbe     noNeedToRound
            mov     Digits[rbx*1], '9'+1
            jmp     whileDigitGT9Test
whileDigitGT9:                      

; Subtract out overflow and add the carry into the previous
; digit (unless we hit the first digit in the number).

            sub     Digits[ rbx*1 ], 10     
            dec     ebx                     
            cmp     ebx, 0                  
            jl      firstDigitInNumber      
                                                                    
            inc     Digits[rbx*1]
            jmp     whileDigitGT9Test

firstDigitInNumber:

; If we get to this point, then we've hit the first
; digit in the number.  So we've got to shift all
; the characters down one position in the string of
; bytes and put a "1" in the first character position.
            
            mov     ebx, 17
repeatUntilEBXeq0:
            
            mov     al, Digits[rbx*1]
            mov     Digits[rbx*1+1], al
            dec     ebx
            jnz     repeatUntilEBXeq0
                    
            mov     Digits, '1'
            inc     Exponent         ; Because we added a digit.
            jmp     noNeedToRound
            
                    
whileDigitGT9Test:
            cmp     Digits[rbx], '9' ; Overflow if char > '9'.
            ja      whileDigitGT9 
            
noNeedToRound:      
            
; Okay, emit the string at this point.  This is pretty easy
; since all we really need to do is copy data from the
; digits array and add an exponent (plus a few other simple chars).
            
            xor     ecx, ecx    ; Count output mantissa digits.
            mov     rdi, buffer
            xor     edx, edx    ; Count output chars.
            mov     al, Sign
            cmp     al, '-'
            je      noMinus
            
            mov     al, ' '
                    
noMinus:    mov     [rdi], al
            
; Output the first character and a following decimal point
; if there are more than two mantissa digits to output.
            
            mov     al, Digits
            mov     [rdi+1], al
            add     rdi, 2
            add     edx, 2
            inc     ecx
            cmp     ecx, MantSize
            je      noDecPt
            
            mov     al, '.'
            mov     [rdi], al
            inc     rdi
            inc     edx     
                                    
noDecPt:
            
; Output any remaining mantissa digits here.
; Note that if the caller requests the output of
; more than 18 digits, this routine will output zeros
; for the additional digits.
            
            jmp     whileECXltMantSizeTest
            
whileECXltMantSize:
            
            mov     al, '0'
            cmp     ecx, 18
            jae     justPut0

            mov     al, Digits[ rcx*1 ]
                    
justPut0:
            mov     [rdi], al
            inc     rdi
            inc     ecx
            inc     edx
            
whileECXltMantSizeTest:
            cmp     ecx, MantSize
            jb      whileECXltMantSize

; Output the exponent:
            
            mov     byte ptr [rdi], 'e'
            inc     rdi
            inc     edx
            mov     al, '+'
            cmp     Exponent, 0
            jge     noNegExp
            
            mov     al, '-'
            neg     Exponent
                                            
noNegExp:
            mov     [rdi], al
            inc     rdi
            inc     edx
            
            mov     eax, Exponent
            mov     ecx, expDigs
            call    expToBuf
            jc      error
                    
exit_eToBuf:
            mov     rsi, rsiSave
            mov     rcx, rcxSave
            mov     rbx, rbxSave
            mov     rax, fWidth
            mov     rdx, expDigs
            leave
            clc
            ret

strOvfl:    mov     rax, -3
            jmp     error

badWidth:   mov     rax, -2
            jmp     error
	
voor:       mov     rax, -1
error:      mov     rsi, rsiSave
            mov     rcx, rcxSave
            mov     rbx, rbxSave
            mov     rdx, expDigs
            leave
            stc
            ret

e10ToStr   endp
  • The r10ToStr function call will need the following arguments:

  • The algorithm for emitting the string differs for values with negative and non-negative exponents. Negative exponents are probably the easiest to process. Here’s the algorithm for emitting values with a negative exponent:

    1. The function begins by adding 3 to decDigits.

    2. If decDigits is less than 4, the function sets it to 4 as a default value.3

    3. If decDigits is greater than fWidth, the function emits fWidth "#" characters to the string and returns.

    4. If decDigits is less than fWidth, then output (fWidth - decDigits) padding characters (fill) to the output string.

    5. If r10 was negative, emit -0. to the string; otherwise, emit 0. to the string (with a leading space in front of the 0 if non-negative).

    6. Next, output the digits from the converted number. If the field width is less than 21 (18 digits plus the 3 leading 0. or -0. characters), then the function outputs the specified (fWidth) characters from the converted digit string. If the width is greater than 21, the function emits all 18 digits from the converted digits and follows it by however many 0 characters are necessary to fill out the field width.

    7. Finally, the function zero-terminates the string and returns.

Floating-Point Value to Exponential Form

  • Converting a floating-point value to exponential (scientific) form is a bit easier than converting it to decimal form. The mantissa always takes the form sx.y where s is a hyphen or a space, x is exactly one decimal digit, and y is one or more decimal digits.

  • The FPDigits function does almost all the work to create this string. The exponential conversion function needs to output the mantissa string with sign and decimal point characters and then output the decimal exponent for the number.

  • This code block from the previous code shows how to do it -

; Exponent output helper function
;
; Cut and pasted from previous code block

;*************************************************************
;
; expToBuf (hacky function)-
;
;  Unsigned integer to buffer.
; Used to output up to 4-digit exponents.
;
; Inputs:
;
;    EAX:   Unsigned integer to convert
;    ECX:   Print width 1-4
;    RDI:   Points at buffer.
;
;    FPU:	Uses FPU stack.
;
; Returns:
;
;    RDI:	Points at end of buffer.
;

expToBuf    proc

expWidth    equ     <[rbp+16]>
exp         equ     <[rbp+8]>
bcd         equ     <[rbp-16]>

            push    rdx
            push    rcx     ;At [RBP+16]
            push    rax     ;At [RBP+8]
            push    rbp
            mov     rbp, rsp
            sub     rsp, 16

; Verify exponent digit count is in the range 1-4:

            cmp     rcx, 1
            jb      badExp
            cmp     rcx, 4
            ja      badExp
            mov     rdx, rcx
            
; Verify the actual exponent will fit in the number of digits:

            cmp     rcx, 2
            jb      oneDigit
            je      twoDigits
            cmp     rcx, 3
            ja      fillZeros       ;4 digits, no error
            cmp     eax, 1000
            jae     badExp
            jmp     fillZeros
            
oneDigit:   cmp     eax, 10
            jae     badExp
            jmp     fillZeros
            
twoDigits:  cmp     eax, 100
            jae     badExp
            
            
; Fill in zeros for exponent:

fillZeros:  mov     byte ptr [rdi+rcx*1-1], '0'
            dec     ecx
            jnz     fillZeros
            
; Point RDI at the end of the buffer:

            lea     rdi, [rdi+rdx*1-1]
            mov     byte ptr [rdi+1], 0
            push    rdi                 ;Save pointer to end

; Quick test for zero to handle that special case:

            test    eax, eax
            jz      allDone
            
; The number to convert is non-zero.
; Use BCD load and store to convert
; the integer to BCD:

            fild    dword ptr exp   ;Get integer value
            fbstp   tbyte ptr bcd   ;Convert to BCD
            
; Begin by skipping over leading zeros in
; the BCD value (max 10 digits, so the most
; significant digit will be in the HO nibble
; of byte 4).

            mov     eax, bcd        ;Get exponent digits
            mov     ecx, expWidth   ;Number of total digits
            
OutputExp:  mov     dl, al
            and     dl, 0fh
            or      dl, '0'
            mov     [rdi], dl
            dec     rdi
            shr     ax, 4
            jnz     OutputExp


; Zero-terminte the string and return:
            
allDone:    pop     rdi
            leave
            pop     rax
            pop     rcx
            pop     rdx
            clc
            ret
            
badExp:     leave
            pop     rax
            pop     rcx
            pop     rdx
            stc
            ret
            
expToBuf    endp


;***********************************************************
;                                                           
; eToStr (cleaner function)-                                                   
;                                                           
; Converts a REAL10 floating-point number to the       
; corresponding string of digits.  Note that this           
; function always emits the string using scientific                  
; notation, use the r10ToStr routine for decimal notation.   
;                                                           
; On Entry:                                                 
;                                                           
;    e10-           Real10 value to convert.
;                   Passed in ST(0)                     
;                                                           
;    width-         Field width for the number (note that this   
;                   is an *exact* field width, not a minimum     
;                   field width).
;                   Passed in RAX (LO 32 bits).                                
;                                                           
;    fill-          Padding character if the number is smaller   
;                   than the specified field width.
;                   Passed in RCX.                  
;                                                                            
;    buffer-        e10ToStr stores the resulting characters in  
;                   this buffer (passed in RDI).
;                                                           
;    expDigs-       Number of exponent digits (2 for real4,     
;                   3 for real8, and 4 for real10).
;                   Passed in RDX (LO 8 bits)             
;                                                                            
;
;    maxLength-     Maximum buffer size.
;                   Passed in R8.                                     
; On Exit: 
;
;    RDI-           Points at end of converted string.                                                 
;                                                           
;    Buffer contains the newly formatted string.  If the    
;    formatted value does not fit in the width specified,   
;    e10ToStr will store "#" characters into this string.
;
;    If there was an error, EAX contains -1, -2, or -3
;    denoting the error (value out of range, bad width,
;    or string overflow, respectively).   
;                                                           
;-----------------------------------------------------------
;                                                           
; Unlike the integer to string conversions, this routine    
; always right justifies the number in the specified        
; string.  Width must be a positive number, negative        
; values are illegal (actually, they are treated as         
; *really* big positive numbers which will always raise     
; a string overflow exception.                              
;                                                           
;***********************************************************

e10ToStr    proc

fWidth      equ     <[rbp-8]>       ;RAX
buffer      equ     <[rbp-16]>      ;RDI
expDigs     equ     <[rbp-24]>      ;RDX
rbxSave     equ     <[rbp-32]>
rcxSave     equ     <[rbp-40]>
rsiSave     equ     <[rbp-48]>
Exponent    equ     <dword ptr [rbp-52]>
MantSize    equ     <dword ptr [rbp-56]>
Sign        equ     <byte ptr [rbp-60]>
Digits      equ     <byte ptr [rbp-128]>

            push    rbp
            mov     rbp, rsp
            sub     rsp, 128
            
            mov     buffer, rdi
            mov     rsiSave, rsi
            mov     rcxSave, rcx
            mov     rbxSave, rbx
            mov     fWidth, rax
            mov     expDigs, rdx

            cmp     eax, r8d
            jae     strOvfl
            mov     byte ptr [rdi+rax*1], 0 ; 0-terminate str
            
            
            
; First, make sure the width isn't zero.
            
            test    eax, eax
            jz      voor

; Just to be on the safe side, don't allow widths greater 
; than 1024:

            cmp     eax, 1024
            ja      badWidth

; Okay, do the conversion.

            lea     rdi, Digits     ; Store result string here.
            call    FPDigits        ; Convert e80 to digit str.
            mov     Exponent, eax   ; Save away exponent result.
            mov     Sign, cl        ; Save mantissa sign char.

; Verify that there is sufficient room for the mantissa's sign,
; the decimal point, two mantissa digits, the "E", 
; and the exponent's sign.  Also add in the number of digits
; required by the exponent (2 for real4, 3 for real8, 4 for 
; real10).
;
; -1.2e+00    :real4
; -1.2e+000   :real8
; -1.2e+0000  :real10
            
            
            mov     ecx, 6          ; Char posns for above chars.
            add     ecx, expDigs    ; # of digits for the exp.
            cmp     ecx, fWidth
            jbe     goodWidth
            
; Output a sequence of "#...#" chars (to the specified width)
; if the width value is not large enough to hold the 
; conversion:

            mov     ecx, fWidth
            mov     al, '#'
            mov     rdi, buffer
fillPound:  mov     [rdi], al
            inc     rdi
            dec     ecx
            jnz     fillPound
            jmp     exit_eToBuf


; Okay, the width is sufficient to hold the number, do the
; conversion and output the string here:

goodWidth:
            
            mov     ebx, fWidth     ; Compute the # of mantissa
            sub     ebx, ecx        ; digits to display.
            add     ebx, 2          ; ECX allows for 2 mant digs.
            mov     MantSize,ebx
                    
            
; Round the number to the specified number of print positions.
; (Note: since there are a maximum of 18 significant digits,
;  don't bother with the rounding if the field width is greater
;  than 18 digits.)
            
            
            cmp     ebx, 18
            jae     noNeedToRound
                    
; To round the value to the number of significant digits,
; go to the digit just beyond the last one we are considering
; (ebx currently contains the number of decimal positions)
; and add 5 to that digit.  Propagate any overflow into the
; remaining digit positions.
            
            mov     al, Digits[rbx*1] ; Get least sig digit + 1.
            add     al, 5             ; Round (e.g., +0.5 ).
            cmp     al, '9'
            jbe     noNeedToRound
            mov     Digits[rbx*1], '9'+1
            jmp     whileDigitGT9Test
whileDigitGT9:                      

; Subtract out overflow and add the carry into the previous
; digit (unless we hit the first digit in the number).

            sub     Digits[ rbx*1 ], 10     
            dec     ebx                     
            cmp     ebx, 0                  
            jl      firstDigitInNumber      
                                                                    
            inc     Digits[rbx*1]
            jmp     whileDigitGT9Test

firstDigitInNumber:

; If we get to this point, then we've hit the first
; digit in the number.  So we've got to shift all
; the characters down one position in the string of
; bytes and put a "1" in the first character position.
            
            mov     ebx, 17
repeatUntilEBXeq0:
            
            mov     al, Digits[rbx*1]
            mov     Digits[rbx*1+1], al
            dec     ebx
            jnz     repeatUntilEBXeq0
                    
            mov     Digits, '1'
            inc     Exponent         ; Because we added a digit.
            jmp     noNeedToRound
            
                    
whileDigitGT9Test:
            cmp     Digits[rbx], '9' ; Overflow if char > '9'.
            ja      whileDigitGT9 
            
noNeedToRound:      
            
; Okay, emit the string at this point.  This is pretty easy
; since all we really need to do is copy data from the
; digits array and add an exponent (plus a few other simple chars).
            
            xor     ecx, ecx    ; Count output mantissa digits.
            mov     rdi, buffer
            xor     edx, edx    ; Count output chars.
            mov     al, Sign
            cmp     al, '-'
            je      noMinus
            
            mov     al, ' '
                    
noMinus:    mov     [rdi], al
            
; Output the first character and a following decimal point
; if there are more than two mantissa digits to output.
            
            mov     al, Digits
            mov     [rdi+1], al
            add     rdi, 2
            add     edx, 2
            inc     ecx
            cmp     ecx, MantSize
            je      noDecPt
            
            mov     al, '.'
            mov     [rdi], al
            inc     rdi
            inc     edx     
                                    
noDecPt:
            
; Output any remaining mantissa digits here.
; Note that if the caller requests the output of
; more than 18 digits, this routine will output zeros
; for the additional digits.
            
            jmp     whileECXltMantSizeTest
            
whileECXltMantSize:
            
            mov     al, '0'
            cmp     ecx, 18
            jae     justPut0

            mov     al, Digits[ rcx*1 ]
                    
justPut0:
            mov     [rdi], al
            inc     rdi
            inc     ecx
            inc     edx
            
whileECXltMantSizeTest:
            cmp     ecx, MantSize
            jb      whileECXltMantSize

; Output the exponent:
            
            mov     byte ptr [rdi], 'e'
            inc     rdi
            inc     edx
            mov     al, '+'
            cmp     Exponent, 0
            jge     noNegExp
            
            mov     al, '-'
            neg     Exponent
                                            
noNegExp:
            mov     [rdi], al
            inc     rdi
            inc     edx
            
            mov     eax, Exponent
            mov     ecx, expDigs
            call    expToBuf
            jc      error
                    
exit_eToBuf:
            mov     rsi, rsiSave
            mov     rcx, rcxSave
            mov     rbx, rbxSave
            mov     rax, fWidth
            mov     rdx, expDigs
            leave
            clc
            ret

strOvfl:    mov     rax, -3
            jmp     error

badWidth:   mov     rax, -2
            jmp     error
            
voor:       mov     rax, -1
error:      mov     rsi, rsiSave
            mov     rcx, rcxSave
            mov     rbx, rbxSave
            mov     rdx, expDigs
            leave
            stc
            ret

e10ToStr   endp

String-to-Numeric Conversion

Decimal Strings to Integers

  • The basic algorithm to convert a string containing decimal digits to a number is the following:

    1. Initialize an accumulator variable to 0.

    2. Skip any leading spaces or tabs in the string.

    3. Fetch the first character after the spaces or tabs.

    4. If the character is not a numeric digit, return an error. If the character is a numeric digit, fall through to step 5.

    5. Convert the numeric character to a numeric value (using AND 0Fh).

    6. Set the accumulator = (accumulator × 10) + current numeric value.

    7. If overflow occurs, return and report an error. If no overflow occurs, fall to step 8.

    8. Fetch the next character from the string.

    9. If the character is a numeric digit, go back to step 5, else fall through to step 10.

    10. Return success, with accumulator containing the converted value.

  • For signed integer input, you use this same algorithm with the following modifications:

    • If the first non-space or tab character is a hyphen (-), set a flag denoting that the number is negative and skip the “-” character (if the first character is not -, then clear the flag).

    • At the end of a successful conversion, if the flag is set, then negate the integer result before return

; String to numeric conversion

        option  casemap:none

false       =       0
true        =       1
tab         =       9
nl          =       10

            .const
fmtStr1     byte    "strtou: String='%s'", nl
            byte    "    value=%I64u", nl, 0
            
fmtStr2     byte    "Overflow: String='%s'", nl
            byte    "    value=%I64x", nl, 0
            
fmtStr3     byte    "strtoi: String='%s'", nl
            byte    "    value=%I64i",nl, 0
                    
unexError   byte    "Unexpected error in program", nl, 0
            
value1      byte    "  1", 0
OFvalue     byte    "18446744073709551616", 0
            
ivalue1     byte    "  -1", 0
OFivalue    byte    "-9223372036854775808", 0
            
            .data
buffer      byte    30 dup (?)
            
            .code
            externdef printf:proc
            
; strtou-
;  Converts string data to a 64-bit unsigned integer.
;
; Input-
;   RDI-    Pointer to buffer containing string to convert
;
; Output-
;   RAX-    Contains converted string (if success), error code
;           if an error occurs.
;
;   RDI-    Points at first char beyond end of numeric string.
;           If error, RDI's value is restored to original value.
;           Caller can check character at [RDI] after a
;           successful result to see if the character following
;           the numeric digits is a legal numeric delimiter.
;
;   C       (carry flag) Set if error occurs, clear if
;           conversion was successful. On error, RAX will
;           contain 0 (illegal initial character) or
;           0ffffffffffffffffh (overflow).

strtou      proc
            push    rdi      ;In case we have to restore RDI
            push    rdx      ;Munged by mul 
            push    rcx      ;Holds input char
            
            xor     edx, edx ;Zero extends!
            xor     eax, eax ;Zero extends!
            
; The following loop skips over any whitespace (spaces and
; tabs) that appear at the beginning of the string.

            dec     rdi      ;Because of inc below.
skipWS:     inc     rdi
            mov     cl, [rdi]
            cmp     cl, ' '
            je      skipWS
            cmp     al, tab
            je      skipWS
            
; If we don't have a numeric digit at this point,
; return an error.

            cmp     cl, '0'  ;Note: '0' < '1' < ... < '9'
            jb      badNumber
            cmp     cl, '9'
            ja      badNumber
            
; Okay, the first digit is good. Convert the string
; of digits to numeric form:

convert:    and     ecx, 0fh ;Convert to numeric in RCX
            mul     ten      ;Accumulator *= 10
            jc      overflow
            add     rax, rcx ;Accumulator += digit
            jc      overflow
            inc     rdi      ;Move on to next character
            mov     cl, [rdi]
            cmp     cl, '0'
            jb      endOfNum
            cmp     cl, '9'
            jbe     convert

; If we get to this point, we've successfully converted
; the string to numeric form:

endOfNum:   pop     rcx
            pop     rdx
            
; Because the conversion was successful, this procedure
; leaves RDI pointing at the first character beyond the
; converted digits. As such, we don't restore RDI from
; the stack. Just bump the stack pointer up by 8 bytes
; to throw away RDI's saved value.

            add     rsp, 8
            clc              ;Return success in carry flag
            ret
            
; badNumber- Drop down here if the first character in
;            the string was not a valid digit.

badNumber:  mov     rax, 0
            pop     rcx
            pop     rdx
            pop     rdi
            stc              ;Return error in carry flag
            ret     
                    
overflow:   mov     rax, -1  ;0FFFFFFFFFFFFFFFFh
            pop     rcx
            pop     rdx
            pop     rdi
            stc              ;Return error in carry flag
            ret
                    
ten         qword   10
                    
strtou      endp


; strtoi-
;  Converts string data to a 64-bit signed integer.
;
; Input-
;   RDI-    Pointer to buffer containing string to convert
;
; Output-
;   RAX-    Contains converted string (if success), error code
;           if an error occurs.
;
;   RDI-    Points at first char beyond end of numeric string.
;           If error, RDI's value is restored to original value.
;           Caller can check character at [RDI] after a
;           successful result to see if the character following
;           the numeric digits is a legal numeric delimiter.
;
;   C       (carry flag) Set if error occurs, clear if
;           conversion was successful. On error, RAX will
;           contain 0 (illegal initial character) or
;           0ffffffffffffffffh (overflow).

strtoi      proc
negFlag     equ     <byte ptr [rsp]>
            
            push    rdi      ;In case we have to restore RDI
            sub     rsp, 8
            
; Assume we have a non-negative number.

            mov     negFlag, false

            
; The following loop skips over any whitespace (spaces and
; tabs) that appear at the beginning of the string.

            dec     rdi      ;Because of inc below.
skipWS:     inc     rdi
            mov     al, [rdi]
            cmp     al, ' '
            je      skipWS
            cmp     al, tab
            je      skipWS
            
; If the first character we've encountered is '-',
; then skip it, but remember that this is a negative
; number.

            cmp     al, '-'
            jne     notNeg
            mov     negFlag, true
            inc     rdi               ;Skip '-'
            
notNeg:     call    strtou  ;Convert string to integer
            jc      hadError
            
; strtou returned success. Check the negative flag and
; negate the input if the flag contains true.

            cmp     negFlag, true
            jne     itsPosOr0
            
            cmp     rax, tooBig     ;number is too big
            ja      overflow
            neg     rax
itsPosOr0:  add     rsp, 16 ;Success, so don't restore RDI
            clc             ;Return success in carry flag
            ret

; If we have an error, we need to restore RDI from the stack

overflow:   mov     rax, -1 ;Indicate overflow      
hadError:   add     rsp, 8  ;Remove locals
            pop     rdi
            stc             ;Return error in carry flag
            ret 
            
tooBig      qword   7fffffffffffffffh
strtoi      endp
            
                    
            
; Here is the "asmMain" function.

        
            public  asmMain
asmMain     proc
            push    rbp
            mov     rbp, rsp
            sub     rsp, 64         ;Shadow storage
            

; Test unsigned conversions:
            
            lea     rdi, value1
            call    strtou
            jc      UnexpectedError
            
            lea     rcx, fmtStr1
            lea     rdx, value1
            mov     r8, rax
            call    printf
            

            lea     rdi, OFvalue
            call    strtou
            jnc     UnexpectedError
            test    rax, rax        ;Non-zero for overflow
            jz      UnexpectedError
            
            lea     rcx, fmtStr2
            lea     rdx, OFvalue
            mov     r8, rax
            call    printf
            
; Test signed conversions:
            
            lea     rdi, ivalue1
            call    strtoi
            jc      UnexpectedError
            
            lea     rcx, fmtStr3
            lea     rdx, ivalue1
            mov     r8, rax
            call    printf
            
            
            lea     rdi, OFivalue
            call    strtoi
            jnc     UnexpectedError
            test    rax, rax        ;Non-zero for overflow
            jz      UnexpectedError
            
            lea     rcx, fmtStr2
            lea     rdx, OFivalue
            mov     r8, rax
            call    printf
            
            
            jmp     allDone

UnexpectedError:
            lea     rcx, unexError
            call    printf

             
allDone:    leave
            ret     ;Returns to caller
asmMain     endp
            end

Hexadecimal Strings to Numeric Form

  • As was the case for numeric output, hexadecimal input is the easiest numeric input routine to write. The basic algorithm for hexadecimal-string-to-numeric conversion is the following:

    1. Initialize an extended-precision accumulator value to 0.

    2. For each input character that is a valid hexadecimal digit, repeat steps 3 through 6; drop down to step 7 when it is not a valid hexadecimal digit.

    3. Convert the hexadecimal character to a value in the range 0 to 15 (0h to 0Fh).

    4. If the HO 4 bits of the extended-precision accumulator value are nonzero, raise an exception.

    5. Multiply the current extended-precision value by 16 (that is, shift left 4 bits).

    6. Add the converted hexadecimal digit value to the accumulator.

    7. Check the current input character to ensure it is a valid delimiter. Raise an exception if it is not.

; Hexadecimal string to numeric conversion

        option  casemap:none

false       =       0
true        =       1
tab         =       9
nl          =       10

            .const
fmtStr1     byte    "strtoh: String='%s' "
            byte    "value=%I64x", nl, 0
           
fmtStr2     byte    "Error, RAX=%I64x, str='%s'", nl, 0 
fmtStr3     byte    "Error, expected overflow: RAX=%I64x, "
            byte    "str='%s'", nl, 0
             
fmtStr4     byte    "Error, expected bad char: RAX=%I64x, "
            byte    "str='%s'", nl, 0 

hexStr      byte    "1234567890abcdef", 0
hexStrOVFL  byte    "1234567890abcdef0", 0
hexStrBAD   byte    "x123", 0

            
            .code
            externdef printf:proc
            
            
; strtoh-
;  Converts string data to a 64-bit unsigned integer.
;
; Input-
;   RDI-    Pointer to buffer containing string to convert
;
; Output-
;   RAX-    Contains converted string (if success), error code
;           if an error occurs.
;
;   RDI-    Points at first char beyond end of hexadecimal string.
;           If error, RDI's value is restored to original value.
;           Caller can check character at [RDI] after a
;           successful result to see if the character following
;           the numeric digits is a legal numeric delimiter.
;
;   C       (carry flag) Set if error occurs, clear if
;           conversion was successful. On error, RAX will
;           contain 0 (illegal initial character) or
;           0ffffffffffffffffh (overflow).

strtoh      proc
            push    rcx      ;Holds input char
            push    rdx      ;Special mask value
            push    rdi      ;In case we have to restore RDI

; This code will use the value in RDX to test and see if overflow
; will occur in RAX when shifting to the left 4 bits:
            
            mov     rdx, 0F000000000000000h
            xor     eax, eax ;Zero out accumulator.
                        
; The following loop skips over any whitespace (spaces and
; tabs) that appear at the beginning of the string.

            dec     rdi      ;Because of inc below.
skipWS:     inc     rdi
            mov     cl, [rdi]
            cmp     cl, ' '
            je      skipWS
            cmp     al, tab
            je      skipWS
            
; If we don't have a hexadecimal digit at this point,
; return an error.

            cmp     cl, '0'  ;Note: '0' < '1' < ... < '9'
            jb      badNumber
            cmp     cl, '9'
            jbe     convert
            and     cl, 5fh  ;Cheesy LC->UC conversion
            cmp     cl, 'A'
            jb      badNumber
            cmp     cl, 'F'
            ja      badNumber
            sub     cl, 7    ;Maps 41h..46h->3ah..3fh
            
; Okay, the first digit is good. Convert the string
; of digits to numeric form:

convert:    test    rdx, rax ;See if adding in the current
            jnz     overflow ; digit will cause an overflow
            
            and     ecx, 0fh ;Convert to numeric in RCX


; Multiple 64-bit accumulator by 16 and add in new digit:

            shl     rax, 4
            add     al, cl  ;Never overflows outside LO 4 bits
                        
;Move on to next character

            inc     rdi      
            mov     cl, [rdi]
            cmp     cl, '0'
            jb      endOfNum
            cmp     cl, '9'
            jbe     convert
            
            and     cl, 5fh  ;Cheesy LC->UC conversion
            cmp     cl, 'A'
            jb      endOfNum
            cmp     cl, 'F'
            ja      endOfNum
            sub     cl, 7    ;Maps 41h..46h->3ah..3fh
            jmp     convert
            

; If we get to this point, we've successfully converted
; the string to numeric form:

endOfNum:
            
; Because the conversion was successful, this procedure
; leaves RDI pointing at the first character beyond the
; converted digits. As such, we don't restore RDI from
; the stack. Just bump the stack pointer up by 8 bytes
; to throw away RDI's saved value; must also remove

            add     rsp, 8   ;Remove original RDI value
            pop     rdx      ;Restore RDX
            pop     rcx      ;Restore RCX
            clc              ;Return success in carry flag
            ret
            
; badNumber- Drop down here if the first character in
;            the string was not a valid digit.

badNumber:  xor     rax, rax
            jmp     errorExit     

overflow:   or      rax, -1  ;Return -1 as error on overflow
errorExit:  pop     rdi      ;Restore RDI if an error occurs
            pop     rdx
            pop     rcx
            stc              ;Return error in carry flag
            ret
                    
strtoh      endp


            
                    
            
; Here is the "asmMain" function.

        
            public  asmMain
asmMain     proc
            push    rbp
            mov     rbp, rsp
            sub     rsp, 64         ;Shadow storage
            

; Test hexadecimal conversion:
            
            lea     rdi, hexStr
            call    strtoh
            jc      error
            
            lea     rcx, fmtStr1
            mov     r8, rax
            lea     rdx, hexStr
            call    printf
            
; Test overflow conversion:
            
            lea     rdi, hexStrOVFL
            call    strtoh
            jnc     unexpected
            
            lea     rcx, fmtStr2
            mov     rdx, rax
            mov     r8, rdi
            call    printf
            
; Test bad character:
            
            lea     rdi, hexStrBAD
            call    strtoh
            jnc     unexp2
            
            lea     rcx, fmtStr2
            mov     rdx, rax
            mov     r8, rdi
            call    printf
            jmp     allDone
            
unexpected: lea     rcx, fmtStr3
            mov     rdx, rax
            mov     r8, rdi
            call    printf
            jmp     allDone
            
unexp2:     lea     rcx, fmtStr4
            mov     rdx, rax
            mov     r8, rdi
            call    printf
            jmp     allDone
            
error:      lea     rcx, fmtStr2
            mov     rdx, rax
            mov     r8, rdi
            call    printf
            
            
allDone:    leave
            ret     ;Returns to caller
asmMain     endp
            end
  • For hexadecimal string conversions that handle numbers greater than 64 bits, you have to use an extended-precision shift left by 4 bits.

; strtoh128-
;  Converts string data to a 128-bit unsigned integer.
;
; Input-
;   RDI-    Pointer to buffer containing string to convert
;
; Output-
;   RDX:RAX-Contains converted string (if success), error code
;           if an error occurs.
;
;   RDI-    Points at first char beyond end of hex string.
;           If error, RDI's value is restored to original value.
;           Caller can check character at [RDI] after a
;           successful result to see if the character following
;           the numeric digits is a legal numeric delimiter.
;
;   C       (carry flag) Set if error occurs, clear if
;           conversion was successful. On error, RAX will
;           contain 0 (illegal initial character) or
;           0ffffffffffffffffh (overflow).

strtoh128   proc
            push    rbx      ;Special mask value
            push    rcx      ;Input char to process
            push    rdi      ;In case we have to restore RDI

; This code will use the value in RDX to test and see if overflow
; will occur in RAX when shifting to the left 4 bits:
            
            mov     rbx, 0F000000000000000h
            xor     eax, eax ;Zero out accumulator.
            xor     edx, edx
                        
; The following loop skips over any whitespace (spaces and
; tabs) that appear at the beginning of the string.

            dec     rdi      ;Because of inc below.
skipWS:     inc     rdi
            mov     cl, [rdi]
            cmp     cl, ' '
            je      skipWS
            cmp     al, tab
            je      skipWS
            
; If we don't have a hexadecimal digit at this point,
; return an error.

            cmp     cl, '0'  ;Note: '0' < '1' < ... < '9'
            jb      badNumber
            cmp     cl, '9'
            jbe     convert
            and     cl, 5fh  ;Cheesy LC->UC conversion
            cmp     cl, 'A'
            jb      badNumber
            cmp     cl, 'F'
            ja      badNumber
            sub     cl, 7    ;Maps 41h..46h->3ah..3fh
            
; Okay, the first digit is good. Convert the string
; of digits to numeric form:

convert:    test    rdx, rbx ;See if adding in the current
            jnz     overflow ; digit will cause an overflow
            
            and     ecx, 0fh ;Convert to numeric in RCX


; Multiple 64-bit accumulator by 16 and add in new digit:

            shld    rdx, rax, 4
            shl     rax, 4
            add     al, cl  ;Never overflows outside LO 4 bits
                        
;Move on to next character

            inc     rdi      
            mov     cl, [rdi]
            cmp     cl, '0'
            jb      endOfNum
            cmp     cl, '9'
            jbe     convert
            
            and     cl, 5fh  ;Cheesy LC->UC conversion
            cmp     cl, 'A'
            jb      endOfNum
            cmp     cl, 'F'
            ja      endOfNum
            sub     cl, 7    ;Maps 41h..46h->3ah..3fh
            jmp     convert
            

; If we get to this point, we've successfully converted
; the string to numeric form:

endOfNum:
            
; Because the conversion was successful, this procedure
; leaves RDI pointing at the first character beyond the
; converted digits. As such, we don't restore RDI from
; the stack. Just bump the stack pointer up by 8 bytes
; to throw away RDI's saved value; must also remove

            add     rsp, 8   ;Remove original RDI value
            pop     rcx      ;Restore RCX
            pop     rbx      ;Restore RBX
            clc              ;Return success in carry flag
            ret
            
; badNumber- Drop down here if the first character in
;            the string was not a valid digit.

badNumber:  xor     rax, rax
            jmp     errorExit     

overflow:   or      rax, -1  ;Return -1 as error on overflow
errorExit:  pop     rdi      ;Restore RDI if an error occurs
            pop     rcx
            pop     rbx
            stc              ;Return error in carry flag
            ret
                    
strtoh128   endp

Unsigned Decimal Strings to Integers

  • The algorithm for unsigned decimal input is nearly identical to that for hexadecimal input. In fact, the only difference is that you multiply the accumulating value by 10 rather than 16 for each input character.

; 64-bit unsigned decimal string to numeric conversion

        option  casemap:none

false       =       0
true        =       1
tab         =       9
nl          =       10

            .const
fmtStr1     byte    "strtou: String='%s' value=%I64u", nl, 0
fmtStr2     byte    "strtou: error, rax=%d", nl, 0
           
qStr      byte    "12345678901234567", 0

            
            .code
            externdef printf:proc


; strtou-
;  Converts string data to a 64-bit unsigned integer.
;
; Input-
;   RDI-    Pointer to buffer containing string to convert
;
; Output-
;   RAX-    Contains converted string (if success), error code
;           if an error occurs.
;
;   RDI-    Points at first char beyond end of numeric string.
;           If error, RDI's value is restored to original value.
;           Caller can check character at [RDI] after a
;           successful result to see if the character following
;           the numeric digits is a legal numeric delimiter.
;
;   C       (carry flag) Set if error occurs, clear if
;           conversion was successful. On error, RAX will
;           contain 0 (illegal initial character) or
;           0ffffffffffffffffh (overflow).

strtou      proc
            push    rcx      ;Holds input char
            push    rdx      ;Save, used for multiplication
            push    rdi      ;In case we have to restore RDI
            
            xor     rax, rax ;Zero out accumulator
            
                        
; The following loop skips over any whitespace (spaces and
; tabs) that appear at the beginning of the string.

            dec     rdi      ;Because of inc below.
skipWS:     inc     rdi
            mov     cl, [rdi]
            cmp     cl, ' '
            je      skipWS
            cmp     al, tab
            je      skipWS
            
; If we don't have a numeric digit at this point,
; return an error.

            cmp     cl, '0'  ;Note: '0' < '1' < ... < '9'
            jb      badNumber
            cmp     cl, '9'
            ja      badNumber
            
; Okay, the first digit is good. Convert the string
; of digits to numeric form:

convert:    and     ecx, 0fh ;Convert to numeric in RCX

; Multiple 64-bit accumulator by 10

            mul     ten
            test    rdx, rdx        ;Test for overflow
            jnz     overflow

            add     rax, rcx
            jc      overflow
                        
;Move on to next character

            inc     rdi      
            mov     cl, [rdi]
            cmp     cl, '0'
            jb      endOfNum
            cmp     cl, '9'
            jbe     convert

; If we get to this point, we've successfully converted
; the string to numeric form:

endOfNum:
            
; Because the conversion was successful, this procedure
; leaves RDI pointing at the first character beyond the
; converted digits. As such, we don't restore RDI from
; the stack. Just bump the stack pointer up by 8 bytes
; to throw away RDI's saved value; must also remove

            add     rsp, 8   ;Remove original RDI value
            pop     rdx
            pop     rcx      ;Restore RCX
            clc              ;Return success in carry flag
            ret
            
; badNumber- Drop down here if the first character in
;            the string was not a valid digit.

badNumber:  xor     rax, rax
            jmp     errorExit     

overflow:   mov     rax, -1  ;0FFFFFFFFFFFFFFFFh
errorExit:  pop     rdi
            pop     rdx
            pop     rcx
            stc              ;Return error in carry flag
            ret
                    
ten         qword   10

strtou      endp

            
                    
            
; Here is the "asmMain" function.

        
            public  asmMain
asmMain     proc
            push    rbp
            mov     rbp, rsp
            sub     rsp, 64         ;Shadow storage
            

; Test hexadecimal conversion:
            
            lea     rdi, qStr
            call    strtou
            jc      error
            
            lea     rcx, fmtStr1
            mov     r8, rax
            lea     rdx, qStr
            call    printf
            jmp     allDone
            
error:      lea     rcx, fmtStr2
            mov     rdx, rax
            call    printf            
 

allDone:    leave
            ret     ;Returns to caller
asmMain     endp
            end

Extended-Precision Decimal String to Unsigned Integer

  • The algorithm for (decimal) string-to-numeric conversion is the same regardless of integer size. You read a decimal character, convert it to an integer, multiply the accumulating result by 10, and add in the converted character. The only things that change for larger-than-64-bit values are the multiplication by 10 and addition operations.

  • Once you have an unsigned decimal input routine, writing a signed decimal input routine is easy, as described by the following algorithm:

    1. Consume any delimiter characters at the beginning of the input stream.

    2. If the next input character is a minus sign, consume this character and set a flag noting that the number is negative; else just drop down to step 3.

    3. Call the unsigned decimal input routine to convert the rest of the string to an integer.

    4. Check the return result to make sure its HO bit is clear. Raise a value out of range exception if the HO bit of the result is set.

    5. If the code encountered a minus sign in step 2, negate the result.

; 128-bit unsigned decimal string to numeric conversion

        option  casemap:none

false       =       0
true        =       1
tab         =       9
nl          =       10

            .const
fmtStr1     byte    "strtou128: String='%s' value=%I64x%I64x", nl, 0
           
oStr      byte    "340282366920938463463374607431768211455", 0

            
            .code
            externdef printf:proc


; strtou128-
;  Converts string data to a 128-bit unsigned integer.
;
; Input-
;   RDI-    Pointer to buffer containing string to convert
;
; Output-
;   RDX:RAX-Contains converted string (if success), error code
;           if an error occurs.
;
;   RDI-    Points at first char beyond end of numeric string.
;           If error, RDI's value is restored to original value.
;           Caller can check character at [RDI] after a
;           successful result to see if the character following
;           the numeric digits is a legal numeric delimiter.
;
;   C       (carry flag) Set if error occurs, clear if
;           conversion was successful. On error, RAX will
;           contain 0 (illegal initial character) or
;           0ffffffffffffffffh (overflow).

strtou128   proc
accumulator equ     <[rbp-16]>
partial     equ     <[rbp-24]>
            push    rcx      ;Holds input char
            push    rdi      ;In case we have to restore RDI
            push    rbp
            mov     rbp, rsp
            sub     rsp, 24  ;Accumulate result here
            
            xor     edx, edx ;Zero extends!
            mov     accumulator, rdx
            mov     accumulator[8], rdx
                        
; The following loop skips over any whitespace (spaces and
; tabs) that appear at the beginning of the string.

            dec     rdi      ;Because of inc below.
skipWS:     inc     rdi
            mov     cl, [rdi]
            cmp     cl, ' '
            je      skipWS
            cmp     al, tab
            je      skipWS
            
; If we don't have a numeric digit at this point,
; return an error.

            cmp     cl, '0'  ;Note: '0' < '1' < ... < '9'
            jb      badNumber
            cmp     cl, '9'
            ja      badNumber
            
; Okay, the first digit is good. Convert the string
; of digits to numeric form:

convert:    and     ecx, 0fh ;Convert to numeric in RCX

; Multiple 128-bit accumulator by 10

            mov     rax, accumulator 
            mul     ten
            mov     accumulator, rax
            mov     partial, rdx    ;Save partial product
            mov     rax, accumulator[8]
            mul     ten
            jc      overflow1
            add     rax, partial
            mov     accumulator[8], rax
            jc      overflow1

; Add in the current character to the 128-bit accumulator
            
            mov     rax, accumulator
            add     rax, rcx
            mov     accumulator, rax
            mov     rax, accumulator[8]
            adc     rax, 0
            mov     accumulator[8], rax
            jc      overflow2
            
;Move on to next character

            inc     rdi      
            mov     cl, [rdi]
            cmp     cl, '0'
            jb      endOfNum
            cmp     cl, '9'
            jbe     convert

; If we get to this point, we've successfully converted
; the string to numeric form:

endOfNum:
            
; Because the conversion was successful, this procedure
; leaves RDI pointing at the first character beyond the
; converted digits. As such, we don't restore RDI from
; the stack. Just bump the stack pointer up by 8 bytes
; to throw away RDI's saved value; must also remove

            mov     rax, accumulator
            mov     rdx, accumulator[8]
            leave
            add     rsp, 8   ;Remove original RDI value
            pop     rcx      ;Restore RCX
            clc              ;Return success in carry flag
            ret
            
; badNumber- Drop down here if the first character in
;            the string was not a valid digit.

badNumber:  xor     rax, rax
            xor     rdx, rdx
            jmp     errorExit     

overflow1:  mov     rax, -1
	cqo
            jmp     errorExit
                                
overflow2:  mov     rax, -2  ;0FFFFFFFFFFFFFFFEh
            cqo		 ;Just to be consistent.
errorExit:  leave            ;Remove accumulator from stack
            pop     rdi
            pop     rcx
            stc              ;Return error in carry flag
            ret
                    
ten         qword   10

strtou128   endp
 
            
; Here is the "asmMain" function.

        
            public  asmMain
asmMain     proc
            push    rbp
            mov     rbp, rsp
            sub     rsp, 64         ;Shadow storage
            

; Test hexadecimal conversion:
            
            lea     rdi, oStr
            call    strtou128
	jc	badConversion
            
            lea     rcx, fmtStr1
            mov     r8, rdx
            mov     r9, rax
            lea     rdx, oStr
            call    printf
	jmp	allDone
	
convErr	byte	"Conversion error: %zd", nl, 0

badConversion:
	lea	rcx, convErr
	mov	rdx, rax
	call	printf            
 

             
allDone:    leave
            ret     ;Returns to caller
asmMain     endp
            end

Real String to Floating-Point

  • Converting a string of characters representing a floating-point number to the 80-bit real10 format is slightly easier than the real10-to-string conversion. The algorithm to do the conversion is the following:

  1. Begin by stripping away any leading space or tab characters (and any other delimiters).

  2. Check for a leading plus (+) or minus (-) sign character. Skip it if one is present. Set a sign flag to true if the number is negative (false if non-negative).

  3. Initialize an exponent value to –18. The algorithm will create a leftjustified packed BCD value from the mantissa digits in the string to provide to the fbld instruction, and left-justified packed BCD values are always greater than or equal to 10^18. Initializing the exponent to –18 accounts for this.

  4. Initialize a significant-digit-counter variable that counts the number of significant digits processed thus far to 18.

  5. If the number begins with any leading zeros, skip over them (do not change the exponent or significant digit counters for leading zeros to the left of the decimal point).

  6. If the scan encounters a decimal point after processing any leading zeros, go to step 11; else fall through to step 7.

  7. For each nonzero digit to the left of the decimal point, if the significant digit counter is not zero, insert the nonzero digit into a “digit string” array at the position specified by the significant digit counter (minus 1). Note that this will insert the characters into the string in a reversed position.

  8. For each digit to the left of the decimal point, increment the exponent value (originally initialized to –18) by 1.

  9. If the significant digit counter is not zero, decrement the significant digit counter (this will also provide the index into the digit string array).

  10. If the first nondigit encountered is not a decimal point, skip to step 14.

  11. Skip over the decimal point character.

  12. For each digit encountered to the right of the decimal point, continue adding the digits (in reverse order) to the digit string array as long as the significant digit counter is not zero. If the significant digit counter is greater than zero, decrement it. Also, decrement the exponent value.

  13. If the algorithm hasn’t encountered at least one decimal digit by this point, report an illegal character exception and return.

  14. If the current character is not e or E, then go to step 20. Otherwise, skip over the e or E character and continue with step 15.

  15. If the next character is + or -, skip over it. Set a flag to true if the sign character is -, and set it to false otherwise (note that this exponent sign flag is different from the mantissa sign flag set earlier in this algorithm).

  16. If the next character is not a decimal digit, report an error.

  17. Convert the string of digits (starting with the current decimal digit character) to an integer.

  18. Add the converted integer to the exponent value (which was initialized to –18 at the start of this algorithm).

  19. If the exponent value is outside the range –4930 to +4930, report an out-of-range exception.

  20. Convert the digit string array of characters to an 18-digit (9-byte) packed BCD value by stripping the HO 4 bits of each character, merging pairs of characters into a single byte (by shifting the odd-indexed byte to the left 4 bits and logically ORing with the even-indexed byte of each pair), and then setting the HO (10th) byte to 0.

  21. Convert the packed BCD value to a real10 value (using the fbld instruction).

  22. Take the absolute value of the exponent (though preserve the sign of the exponent). This value will be 13 bits or less (4096 has bit 12 set, so 4930 or less will have some combination of bits 0 to 13 set to 1, with all other bits 0).

  23. If the exponent was positive, then for each set bit in the exponent, multiply the current real10 value by 10 raised to the power specified by that bit. For example, if bits 12, 10, and 1 are set, multiply the real10 value by 10^4096, 10^1024, and 10^2.

  24. If the exponent was negative, then for each set bit in the exponent, divide the current real10 value by 10 raised to the power specified by that bit. For example, if bits 12, 10, and 1 are set, divide the real10 value by 10^4096, 10^1024, and 10^2.

  25. If the mantissa is negative (the first sign flag set at the beginning of the algorithm), then negate the floating-point number.

; Real string to floating-point conversion

        option  casemap:none

false       =       0
true        =       1
tab         =       9
nl          =       10

            .const
fmtStr1     byte    "strToR10: str='%s', value=%e", nl, 0
           
fStr1a      byte    "1.234e56",0
fStr1b      byte    "-1.234e56",0
fStr1c      byte    "1.234e-56",0
fStr1d      byte    "-1.234e-56",0
fStr2a      byte    "1.23",0
fStr2b      byte    "-1.23",0
fStr3a      byte    "1",0
fStr3b      byte    "-1",0
fStr4a      byte    "0.1",0
fStr4b      byte    "-0.1",0
fStr4c      byte    "0000000.1",0
fStr4d      byte    "-0000000.1",0
fStr4e      byte    "0.1000000",0
fStr4f      byte    "-0.1000000",0
fStr4g      byte    "0.0000001",0
fStr4h      byte    "-0.0000001",0
fStr4i      byte    ".1",0
fStr4j      byte    "-.1",0

values      qword   fStr1a, fStr1b, fStr1c, fStr1d,
                    fStr2a, fStr2b,
                    fStr3a, fStr3b,
                    fStr4a, fStr4b, fStr4c, fStr4d,
                    fStr4e, fStr4f, fStr4g, fStr4h,
                    fStr4i, fStr4j,
                    0

            align   4
PotTbl      real10  1.0e+4096,
                    1.0e+2048,
                    1.0e+1024,
                    1.0e+512,
                    1.0e+256,
                    1.0e+128,
                    1.0e+64,
                    1.0e+32,
                    1.0e+16,
                    1.0e+8,
                    1.0e+4,
                    1.0e+2,
                    1.0e+1,
                    1.0e+0

            .data
r8Val       real8   ?

            
            .code
            externdef printf:proc

; Used for debugging:

print       proc
            push    rax
            push    rbx
            push    rcx
            push    rdx
            push    r8
            push    r9
            push    r10
            push    r11
            
            push    rbp
            mov     rbp, rsp
            sub     rsp, 40
            and     rsp, -16
            
            mov     rcx, [rbp+72]   ;Return address
            call    printf
            
            mov     rcx, [rbp+72]
            dec     rcx
skipTo0:    inc     rcx
            cmp     byte ptr [rcx], 0
            jne     skipTo0
            inc     rcx
            mov     [rbp+72], rcx
            
            leave
            pop     r11
            pop     r10
            pop     r9
            pop     r8
            pop     rdx
            pop     rcx
            pop     rbx
            pop     rax
            ret
print       endp

;*********************************************************
;                                                         
; strToR10-                                                   
;                                                         
; RSI points at a string of characters that represent a   
; floating point value.  This routine converts that string
; to the corresponding FP value and leaves the result on  
; the top of the FPU stack.  On return, ESI points at the 
; first character this routine couldn't convert.          
;                                                         
; Like the other ATOx routines, this routine raises an    
; exception if there is a conversion error or if ESI      
; contains NULL.                                          
;                                                         
;*********************************************************


strToR10    proc

sign        equ     <cl>
expSign     equ     <ch>

DigitStr    equ     <[rbp-24]>
BCDValue    equ     <[rbp-34]>
rsiSave     equ     <[rbp-44]>

            push    rbp
            mov     rbp, rsp
            sub     rsp, 44
	            
            push    rbx
            push    rcx
            push    rdx
            push    r8
            push    rax
            
; Verify that RSI is not NULL.
            
            test    rsi, rsi
            jz      refNULL
                    
; Zero out the DigitStr and BCDValue arrays.
            
            xor     rax, rax
            mov     qword ptr DigitStr, rax
            mov     qword ptr DigitStr[8], rax
            mov     dword ptr DigitStr[16], eax
            
            mov     qword ptr BCDValue, rax
            mov     word ptr BCDValue[8], ax
            
; Skip over any leading space or tab characters in the sequence.
            
            dec     rsi
whileDelimLoop:
            inc     rsi
            mov     al, [rsi]
            cmp     al, ' '
            je      whileDelimLoop
            cmp     al, tab
            je      whileDelimLoop
                    
            
            
; Check for + or -
            
            cmp     al, '-'
            sete    sign
            je      doNextChar
            cmp     al, '+'
            jne     notPlus
doNextChar: inc     rsi             ; Skip the '+' or '-'
            mov     al, [rsi]

notPlus:
            
; Initialize edx with -18 since we have to account
; for BCD conversion (which generates a number *10^18 by
; default). EDX holds the value's decimal exponent.
            
            mov     rdx, -18
            
; Initialize ebx with 18, the number of significant
; digits left to process and also the index into the
; DigitStr array.
            
            mov     ebx, 18         ;Zero extends!
            
; At this point we're beyond any leading sign character.
; Therefore, the next character must be a decimal digit
; or a decimal point.

            mov     rsiSave, rsi    ; Save to look ahead 1 digit.
            cmp     al, '.'
            jne     notPeriod

; If the first character is a decimal point, then the
; second character needs to be a decimal digit.
                            
            inc     rsi
            mov     al, [rsi]
                    
notPeriod:
            cmp     al, '0'
            jb      convError
            cmp     al, '9'
            ja      convError
            mov     rsi, rsiSave    ; Go back to orig char
            mov     al, [rsi]
            jmp     testWhlAL0

; Eliminate any leading zeros (they do not affect the value or
; the number of significant digits).
            
            
whileAL0:   inc     rsi
            mov     al, [rsi]
testWhlAL0: cmp     al, '0'
            je      whileAL0
            
; If we're looking at a decimal point, we need to get rid of the
; zeros immediately after the decimal point since they don't
; count as significant digits.  Unlike zeros before the decimal
; point, however, these zeros do affect the number's value as
; we must decrement the current exponent for each such zero.
            
            cmp     al, '.'
            jne     testDigit
            
            inc     edx     ;Counteract dec below   
repeatUntilALnot0:
            dec     edx
            inc     rsi
            mov     al, [rsi]
            cmp     al, '0'
            je      repeatUntilALnot0
            jmp     testDigit2
            
        
; If we didn't encounter a decimal point after removing leading
; zeros, then we've got a sequence of digits before a decimal
; point.  Process those digits here.
;
; Each digit to the left of the decimal point increases
; the number by an additional power of ten.  Deal with
; that here.
            
whileADigit:
            inc     edx     

; Save all the significant digits, but ignore any digits
; beyond the 18th digit.
            
            test    ebx, ebx
            jz      Beyond18
            
            mov     DigitStr[ rbx*1 ], al
            dec     ebx
                    
Beyond18:   inc     rsi
            mov     al, [rsi]
                    
testDigit:  
            sub     al, '0'
            cmp     al, 10
            jb      whileADigit

            cmp     al, '.'-'0'
            jne     testDigit2

            inc     rsi             ; Skip over decimal point.
            mov     al, [rsi]
            jmp     testDigit2
            
; Okay, process any digits to the right of the decimal point.
            
            
whileDigit2:
            test    ebx, ebx
            jz      Beyond18_2      
            
            mov     DigitStr[ rbx*1 ], al
            dec     ebx
                    
Beyond18_2: inc     rsi
            mov     al, [rsi]
                    
testDigit2: sub     al, '0'
            cmp     al, 10
            jb      whileDigit2
                            
                    
; At this point, we've finished processing the mantissa.
; Now see if there is an exponent we need to deal with.

            mov     al, [rsi]       
            cmp     al, 'E'
            je      hasExponent
            cmp     al, 'e'
            jne     noExponent
            
hasExponent:
            inc     rsi
            mov     al, [rsi]       ; Skip the "E".
            cmp     al, '-'
            sete    expSign
            je      doNextChar_2
            cmp     al, '+'
            jne     getExponent;
            
doNextChar_2:
            inc     rsi             ;Skip '+' or '-'
            mov     al, [rsi]
                    
            
; Okay, we're past the "E" and the optional sign at this
; point.  We must have at least one decimal digit.
            
getExponent:
            sub     al, '0'
            cmp     al, 10
            jae     convError
            
            xor     ebx, ebx        ; Compute exponent value in ebx.
ExpLoop:    movzx   eax, byte ptr [rsi] ;Zero extends to rax!
            sub     al, '0'
            cmp     al, 10
            jae     ExpDone
            
            imul    ebx, 10
            add     ebx, eax
            inc     rsi
            jmp     ExpLoop
            
            
; If the exponent was negative, negate our computed result.
            
ExpDone:
            cmp     expSign, false
            je      noNegExp
            
            neg     ebx
                    
noNegExp:

; Add in the BCD adjustment (remember, values in DigitStr, when
; loaded into the FPU, are multiplied by 10^18 by default.
; The value in edx adjusts for this).
            
            add     edx, ebx
            
noExponent:
                    
; verify that the exponent is between -4930..+4930 (which
; is the maximum dynamic range for an 80-bit FP value).

            cmp     edx, 4930
            jg      voor            ; Value out of range
            cmp     edx, -4930
            jl      voor
            
            
; Now convert the DigitStr variable (unpacked BCD) to a packed
; BCD value.

            mov     r8, 8
for8:       mov     al, DigitStr[ r8*2 + 2]
            shl     al, 4
            or      al, DigitStr[ r8*2 +1 ]
            mov     BCDValue[ r8*1], al

            dec     r8
            jns     for8

            fbld    tbyte ptr BCDValue
            
            
; Okay, we've got the mantissa into the FPU.  Now multiply the
; Mantissa by 10 raised to the value of the computed exponent
; (currently in edx).
;
; This code uses power of 10 tables to help make the 
; computation a little more accurate.
;
; We want to determine which power of ten is just less than the
; value of our exponent.  The powers of ten we are checking are
; 10**4096, 10**2048, 10**1024, 10**512, etc.  A slick way to
; do this check is by shifting the bits in the exponent
; to the left.  Bit #12 is the 4096 bit.  So if this bit is set,
; our exponent is >= 10**4096.  If not, check the next bit down
; to see if our exponent >= 10**2048, etc.

            mov     ebx, -10 ; Initial index into power of ten table.
            test    edx, edx
            jns     positiveExponent
            
; Handle negative exponents here.
            
            neg     edx
            shl     edx, 19 ; Bits 0..12 -> 19..31
            lea     r8, PotTbl
whileEDXne0:
            add     ebx, 10
            shl     edx, 1
            jnc     testEDX0
            
            fld     real10 ptr [r8][ rbx*1 ]
            fdivp
                    
testEDX0:   test    edx, edx
            jnz     whileEDXne0
            jmp     doMantissaSign


; Handle positive exponents here.
                    
positiveExponent:
            lea     r8, PotTbl
            shl     edx, 19 ; Bits 0..12 -> 19..31.
            jmp     testEDX0_2

whileEDXne0_2:
            add     ebx, 10
            shl     edx, 1
            jnc     testEDX0_2
            
            fld     real10 ptr [r8][ rbx*1 ]
            fmulp
            
testEDX0_2: test    edx, edx
            jnz     whileEDXne0_2


; If the mantissa was negative, negate the result down here.

doMantissaSign:
            cmp     sign, false
            je      mantNotNegative
            
            fchs
                    
mantNotNegative:
            clc             ;Indicate Success
            jmp     Exit    

refNULL:    mov     rax, -3
            jmp     ErrorExit

convError:  mov     rax, -2
            jmp     ErrorExit

voor:       mov     rax, -1 ;Value out of range
            jmp     ErrorExit

illChar:    mov     rax, -4

ErrorExit:  stc                     ;Indicate failure
            mov     [rsp], rax      ;Save error code
Exit:       pop     rax
            pop     r8                      
            pop     rdx
            pop     rcx
            pop     rbx
            leave
            ret

strToR10    endp
             
            
; Here is the "asmMain" function.

        
            public  asmMain
asmMain     proc
            push    rbx
            push    rsi
            push    rbp
            mov     rbp, rsp
            sub     rsp, 64         ;Shadow storage
            

; Test floating-point conversion:
            
            lea     rbx, values
ValuesLp:   cmp     qword ptr [rbx], 0
            je      allDone
            
            mov     rsi, [rbx]
            call    strToR10
            fstp    r8Val
            
            lea     rcx, fmtStr1
            mov     rdx, [rbx]
            mov     r8, qword ptr r8Val
            call    printf
            add     rbx, 8
            jmp     ValuesLp            
 

allDone:    leave
            pop     rsi
            pop     rbx
            ret     ;Returns to caller
asmMain     endp
            end

Last updated