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:
Divide 789 by 10. The quotient is 78, and the remainder is 9.
Save the remainder (9) in a local variable and recursively call the routine with the quotient.
Recursive entry 1: Divide 78 by 10. The quotient is 7, and the remainder is 8.
Save the remainder (8) in a local variable and recursively call the routine with the quotient.
Recursive entry 2: Divide 7 by 10. The quotient is 0, and the remainder is 7.
Save the remainder (7) in a local variable. Because the quotient is 0, don’t call the routine recursively.
Output the remainder value saved in the local variable (7). Return to the caller (recursive entry 1).
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).
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:
Check the sign of the number.
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:
Decimal notation conversion (for example, ± xxx.yyy format)
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:
The function begins by adding 3 to decDigits.
If decDigits is less than 4, the function sets it to 4 as a default value.3
If decDigits is greater than fWidth, the function emits fWidth "#" characters to the string and returns.
If decDigits is less than fWidth, then output (fWidth - decDigits) padding characters (fill) to the output string.
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).
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.
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:
Initialize an accumulator variable to 0.
Skip any leading spaces or tabs in the string.
Fetch the first character after the spaces or tabs.
If the character is not a numeric digit, return an error. If the character is a numeric digit, fall through to step 5.
Convert the numeric character to a numeric value (using AND 0Fh).
Set the accumulator = (accumulator × 10) + current numeric value.
If overflow occurs, return and report an error. If no overflow occurs, fall to step 8.
Fetch the next character from the string.
If the character is a numeric digit, go back to step 5, else fall through to step 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:
Initialize an extended-precision accumulator value to 0.
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.
Convert the hexadecimal character to a value in the range 0 to 15 (0h to 0Fh).
If the HO 4 bits of the extended-precision accumulator value are nonzero, raise an exception.
Multiply the current extended-precision value by 16 (that is, shift left 4 bits).
Add the converted hexadecimal digit value to the accumulator.
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:
Consume any delimiter characters at the beginning of the input stream.
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.
Call the unsigned decimal input routine to convert the rest of the string to an integer.
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.
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:
Begin by stripping away any leading space or tab characters (and any other delimiters).
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).
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.
Initialize a significant-digit-counter variable that counts the number of significant digits processed thus far to 18.
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).
If the scan encounters a decimal point after processing any leading zeros, go to step 11; else fall through to step 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.
For each digit to the left of the decimal point, increment the exponent value (originally initialized to –18) by 1.
If the significant digit counter is not zero, decrement the significant digit counter (this will also provide the index into the digit string array).
If the first nondigit encountered is not a decimal point, skip to step 14.
Skip over the decimal point character.
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.
If the algorithm hasn’t encountered at least one decimal digit by this point, report an illegal character exception and return.
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.
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).
If the next character is not a decimal digit, report an error.
Convert the string of digits (starting with the current decimal digit character) to an integer.
Add the converted integer to the exponent value (which was initialized to –18 at the start of this algorithm).
If the exponent value is outside the range –4930 to +4930, report an out-of-range exception.
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.
Convert the packed BCD value to a real10 value (using the fbld instruction).
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).
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.
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.
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