Search code examples
cassemblyx86masmfpu

Why am I getting nonsensical numbers when performing operations with the FPU x87 instruction set in MASM Assembly?


I've started learning assembly recently, and I'm now learning about the FPU x86 architecture and the FPU stack. I have two simple functions for converting between Celsius and Fahrenheit and vice versa.

I've looked into various different instructions, I've tried variants of the FPU instructions including ones that automatically perform POP operations, and tried to go through the debugger to make sense of what I was seeing. So far to no avail.

.386
.model flat, c

.const
  r8_ftoc real8 0.5555555556 ;5/9
  r8_ctof real8 1.8 ;9/5
  i4_32 dword 32
.code
  fahrentocel PROC
    push ebp
    mov ebp, esp
    fld[r8_ftoc]
    fld real8 ptr [ebp+8] ; load f 
    fild[i4_32] ; load 32    
    fsubp
    fmulp
    pop ebp
    ret
  fahrentocel ENDP

  celtofahren PROC
    push ebp
    mov ebp, esp
    fild real8 ptr [ebp+8] ; load c
    fmul[r8_ctof]
    fiadd[i4_32]
    pop ebp
    ret
  celtofahren endp
END

C code:

extern "C" double fahrentocel(double temp);
extern "C" double celtofahren(double temp);

int main()
{
    double celsius = 30.0;
    double fahrenheit = 212.0;
    double output = fahrentocel(fahrenheit);
    printf("%lf", output);

}

My input for fahrenheit to celsius is 212.0, so the celsius output is 100 and my conversion from celsius to fahrenheit is 30.0, so the result in fahrenheit should be 86.

But instead, I get 562950 and 858993459 respectively for each of the two functions. I don't receive any error codes, so the function seems to perform without exception, which tells me it's likely a logical error with the way I've written my code.


Solution

  • There's a couple of problems here. According to the functions' declarations in the C code that uses them, i.e. :

    extern "C" double fahrentocel(double temp);
    extern "C" double celtofahren(double temp);
    

    Both these functions take a double argument and return a double result. This is fine when it comes to the interface, but your implementation of celtofahren says otherwise :

    fild real8 ptr [ebp+8] ; load c
    

    Here, you load the function's argument onto the FPU stack as an integer, even though you have yourself instructed the C compiler that the function takes a double. The C compiler thus emitted code that pushes a double onto the regular stack, but your assembly reads it as a regular integer due to the fild instruction. Do note that fahrentocel loads the argument correctly.

    Secondly, you're passing a wrong format argument to printf, which should be f for double values - d is used for integers. Thus,

    printf("%d", output);
    

    should become

    printf("%f", output);