Lab 10 -- floating point numbers

Dealing with floating numbers is quite different than integers. Below, you will see how we store and manipulate the floating point numbers.

Part 1

The following line shows how we would assign a label to a floating point value. Notice that the value contains a decimal point.

    val1: dq 12.34
We have seen how to print integer values, and might expect that printing floating point values is a matter of simply loading "val1" into rsi, as we did with an integer, but that does not work. Also, since we are using the printf function, we need to specify the format, and we use "%g" here. It is one of several formats that we could use. The printf function can take multiple types of arguments. For example, these are all valid printf calls from C:

    printf("hello\n");
    printf("%d\n", 3);
    printf("%f\n", 3.4);
The A register is used to indicate more information to printf. We put the value 0 in it before, when printing an integer. For printing a floating point value, we will use the value 1.

Next, let us see how we store and print a floating number in NASM:


        extern printf ; We will use this external function
    
        section .data ; Data section, initialized variables
    
    val1:   dq 12.34 
    mystr2: db "%g", 10, 0 ; String format to use
    
    
        section .text
        global main
    main:
    
        push rbp            ; remember the current rbp value
    
        mov rdi, mystr2     ; the format to use 
        ;mov rsi, [val1] 
        movsd xmm0, [val1]  ; the value to print 
        mov al, 1           ; 1 for a floating point argument 
        call printf
    
        mov rax, 0          ; return value
        pop rbp             ; restore the former rbp value
        ret
Try it out, and verify that it works.

Questions

The use of push and pop instructions in the provided assembly code is related to maintaining the stack alignment as per the x86-64 calling conventions. It ensures that the stack is properly aligned before and after making a function call (see chapter 8 in your book for more, e.g. page 287). Let's discuss why the push and pop instructions are used in the context of your code:

push rbp: This instruction pushes the value of the base pointer (rbp) onto the stack. The purpose of pushing rbp is to save its current value so that it can be restored later (during the pop rbp instruction).

pop rbp: This instruction restores the value of the base pointer (rbp) from the stack. Restoring rbp is necessary to return the stack to its initial state, ensuring proper stack management.

If you remember we used registers like rax, rbx while dealing with integer numbers. Those registers (rax, rbx, rcx, rdx) are general purpose registers in NASM. For floating point numbers, we have a dedicated set of registers (xmm0 to xmm15). They are specifically designed to hold floating numbers and perform operations on them, so we will make use of these registers.

Part 2

Can you put two float values in registers, add them and display the result? Look at the movsd command from the previous example. Use addsd command to add two floating numbers. Finally, to print the result you need to follow the instruction of previous example.

    addsd xmm0, xmm1 ; Add the contents of xmm1 to xmm0 

Then, subtract one floating point number from another and display the result. Use the subsd command to perform subtraction with floating point numbers.

Questions

Part 3

Next, add the float and integer numbers, as shown below, and display the result.

            extern printf
    
            section .data

    val1:   dq 3.14
    val2:   dq 5
    result: dq 0.0
    mystr1: db "%g", 10, 0 ; String format to use
    
            section .text
            global main

    main:
         push   rbp
         mov    rdi, mystr1
         mov    rbx, [val2]
         movsd  xmm0, [val1]
         addsd  xmm0, rax
         movsd  [result], xmm0
         mov    al, 1
         call   printf
         mov    rax, 0
         pop    rbp
         ret
This code will not run. The assembler gives an error message.
Questions

Part 4

Below is code that correctly adds an integer number with the float. Before we can add integer number with float, we need to convert the integer to be of type float. We do that using command cvtsi2sd. Here is an example:

    float1:     dq 3.14 
    integer1:   dq 5 
    result:     dq 0.0 ; to store the result 
    format_str: db "Result: %g", 10, 0 ; String format to use 
    
    ;   ...
        ; Load the floating-point number into xmm0 
        movsd     xmm0, [float1] 
        ; Load the integer into register
        mov       rax, [integer1] 
        ; Convert the integer in xmm1 to a floating-point number 
        cvtsi2sd  xmm1, rax
        ; Add the contents of xmm1 to xmm0 
        addsd     xmm0, xmm1 
        ; Store the result back in memory 
        movsd     [result], xmm0 
Implement this code, compile it, and run it. Verify that it works.

Questions

In this lab, we have learned: