As the second scenario, we will add to our library a function for displaying floating-point values. This function will allow us to display the results of calculations we implement in further scenarios. According to x64 Windows ABI rules, floating-point values should be passed through XMM registers. We will display a single value, so we'll use the XMM0 register.
Prerequisites
Read the section “Creating static libraries”, where the conversion library and main program are described, and the initial code of both is presented. You should be familiar with writing, assembling and running programs in Visual Studio Community or using MASM tools directly. Please read the chapter “Programming in Assembler for x64” for a detailed description of how to prepare the software environment.
Scenario
Extend the conversion library described in “Creating static libraries” with a function which converts the floating-point argument into text. Display the result in a console.
Result
The converted string with a number is displayed in a console.
Start
Make a copy of the int_to_ascii function and introduce modifications described in the following steps.
Step 1
Displaying floating-point numbers is a much more complex task than displaying an integer. We will split it into a conversion of the fractional part and a conversion of the integer part. At the beginning of a function, we'll store the argument in XMM0 into XMM1 to have the original value unchanged.
Let's start with a check to see if the value is positive or negative. Floating-point numbers are stored as absolute values, with the sign bit in the most significant position. The encoding scheme for positive and negative numbers with the same absolute value differs only in the sign bit. To test whether a number is negative, we can use the movmskps instruction, which copies the sign bits from all elements of a vector into the destination register. As our argument is a scalar, the bit we're interested in is at the lowest position. Shifting the register one position to the right, we can execute a conditional jump. If the argument is negative, we'll change it into positive by clearing a sign bit. The andps instruction with the clear_sign_bit variable clears one bit in the XMM1 register.
.data clear_sign_bit dword 07FFFFFFFh, 0FFFFFFFFh, 0FFFFFFFFh, 0FFFFFFFFh ... .code ... ; test if the number is positive or negative movq xmm1, xmm0 movmskps rax, xmm1 rcr rax, 1 jnc float_positive ; change the sign of the scalar andps xmm1, xmmword ptr clear_sign_bit ; do not change the sign float_positive:
Step 2
The conversion will start from the least significant digit of the fractional part, limiting precision to thousandths. We obtain the fractional part by subtracting the integer part from the original argument. An integer is obtained with the cvttss2si instruction, which simply cuts out the fractional part of a number. We store the result in rcx for further use.
.data const1000 real4 1000.0 .code ... ; convert fractional part cvttss2si rax, xmm1 ; convert float to int with truncation mov rcx, rax ; store for conversion of an integer part cvtsi2ss xmm2, rax ; convert back into float subss xmm1, xmm2 ; subtract integer part mulss xmm1, const1000 ; we want three fractional digits cvttss2si rax, xmm1 mov rbx, 10 convert_fraction: dec rdi ; starting from the end of the text (least significant) xor rdx, rdx ; prepare to divide rdx:rax by rbx div rbx ; rax / 10 → remainder in rdx add dl, "0" ; convert remainder into ASCII mov [rdi], dl ; write character to buffer test rax, rax ; test if there is still a value for conversion jne convert_fraction
We separate the fractional and integer parts with a dot.
; add dot dec rdi mov byte ptr [rdi], '.'
Step 3
The integer part is converted with the same algorithm as the fractional, but before we restore its value from rcx.
; convert integer part mov rax, rcx ; restore integer part convert_integer: dec rdi ; starting from the end of the text (least significant) xor rdx, rdx ; prepare to divide rdx:rax by rbx div rbx ; rax / 10 → remainder in rdx add dl, "0" ; convert remainder into ASCII mov [rdi], dl ; write character to buffer test rax, rax ; test if there is still a value for conversion jne convert_integer
After converting the integer part, we need to add “minus” for a negative value. We'll test it again with the same method as at the beginning. For this purpose, we kept the original argument in XMM0.
; test if the number is positive or negative movmskps rax, xmm0 rcr rax, 1 jnc end_float ; add minus if needed dec rdi ; add minus character mov byte ptr [rdi], '-' end_float:
Step 4
The final part, calculating the string length, is the same as in the conversion of integers, so if you made a copy of the function, you could leave it unchanged.
Step 5
Add the floating-point value for conversion into a main program, and call the function. Remember that a floating-point value can't be encoded as an immediate argument of any instruction, so you need to define the variable with a chosen initial value.
.data float_num real4 -123456.789
Result validation
The code of a function should be called from the main program. As a result, you should see the floating point value displayed in a console.
If it does not print the proper value?
To print the result in a console, please remember to pass the argument to the conversion function by XMM0 register, not RCX.