Porting old compiler ftol (float to long) function to C

2.5k views Asked by At

I'm relying on an old implementation that does some calculations and converts float's to int.

However, after replicating the calculations some values are off due different rounding results.

It boils down that the binary is using the following code for converting a float to int (long).

                lea     ecx, [esp+var_8] ; Load Effective Address
                sub     esp, 10h        ; Integer Subtraction
                and     ecx, 0FFFFFFF8h ; Logical AND
                fld     st              ; Load Real
                fistp   qword ptr [ecx] ; Store Integer and Pop
                fild    qword ptr [ecx] ; Load Integer
                mov     edx, [ecx+4]
                mov     eax, [ecx]
                test    eax, eax        ; Logical Compare
                jz      short loc_3     ; Jump if Zero (ZF=1)

loc_1:                             
                fsubp   st(1), st       ; Subtract Real and Pop
                test    edx, edx        ; Logical Compare
                jz      short loc_2 ; Jump if Zero (ZF=1)
                fstp    dword ptr [ecx] ; Store Real and Pop
                mov     ecx, [ecx]
                add     esp, 10h        ; Add
                xor     ecx, 80000000h  ; Logical Exclusive OR
                add     ecx, 7FFFFFFFh  ; Add
                adc     eax, 0          ; Add with Carry
                retn                    ; Return Near from Procedure
; ---------------------------------------------------------------------------

loc_2:                            
                fstp    dword ptr [ecx] ; Store Real and Pop
                mov     ecx, [ecx]
                add     esp, 10h        ; Add
                add     ecx, 7FFFFFFFh  ; Add
                sbb     eax, 0          ; Integer Subtraction with Borrow
                retn                    ; Return Near from Procedure
; ---------------------------------------------------------------------------

loc_3:                             
                test    edx, 7FFFFFFFh  ; Logical Compare
                jnz     short loc_1     ; Jump if Not Zero (ZF=0)
                fstp    dword ptr [ecx] ; Store Real and Pop
                fstp    dword ptr [ecx] ; Store Real and Pop
                add     esp, 10h        ; Add
                retn                    ; Return Near from Procedure

This behaves differently then simply doing unsigned int var = (unsigned int)floatVal;.

I believe this is an old ftol implementation and was done because converting from float to int was very slow and the compiler needed to change the FPU rounding mode.

It looks very similar to this one http://www.libsdl.org/release/SDL-1.2.15/src/stdlib/SDL_stdlib.c

Can anyone assist me in converting the function to C? Or tell me how I can create an inline ASM function with float parameter and return int using Visual Studio. The one in SDL_sdtlib.c has no header and I'm not sure how to call it without function args.

1

There are 1 answers

0
iwolf On

This doesn't exactly answer the questions as asked, but I wanted to start by trying a more thorough English translation. It's not perfect, and there are a few lines where I'm still trying to track down the intent. Everyone please speak up with questions and corrections.

            lea     ecx, [esp+var_8]; Load Effective Address    // make ecx point to somewhere on the stack (I don't know where var_8 is being generated in this case, but I'm guessing it's set such that it makes ecx point to the local stack space allocated on the next line)
            sub     esp, 10h        ; Integer Subtraction       // make room on stack for 16 bytes of local variable -- doesn't all get used but adds padding to allow aligned loads and stores
            and     ecx, 0FFFFFFF8h ; Logical AND               // align pointer in ecx to 8-byte boundary
            fld     st              ; Load Real                 // duplicates whatever was last left (passed by calling convention) on the top of the FPU stack -- st(1) = st(0)
            fistp   qword ptr [ecx] ; Store Integer and Pop     // convert st(0) to *64bit* int (truncate), store in aligned 8 bytes (of local variable space?) pointed to by ecx, and pop off the top value from the FPU stack
            fild    qword ptr [ecx] ; Load Integer              // convert truncated value back to float and leave it sitting on the top of the FPU stack

;// at this point:
;// - st(0) is the truncated float
;// - st(1) is still the original float.
;// - There is a 64bit integer representation pointed to by [ecx]

            mov     edx, [ecx+4]    ;                           // move [bytes 4 thru 7 of integer output] to edx (most significant bytes)
            mov     eax, [ecx]      ;                           // move [bytes 0 thru 3 of integer output] to eax (least significant bytes) -- makes sense, as EAX should hold integer return value in x86 calling conventions
            test    eax, eax        ; Logical Compare           // (http://stackoverflow.com/questions/13064809/the-point-of-test-eax-eax)
            jz      short loc_3     ; Jump if Zero (ZF=1)       // if the least significant 4 bytes are zero, goto loc_3
                                    ;                           // else fall through to loc_1
loc_1:                              ;
            fsubp   st(1), st       ; Subtract Real and Pop     // subtract the truncated float from the original, store in st(1), then pop. (i.e. for 1.25 st(0) ends up 0.25, and the original float is no longer on the FPU stack)
            test    edx, edx        ; Logical Compare           // same trick as earlier, but for the most significant bytes now
            jz      short loc_2     ; Jump if Zero (ZF=1)       // if the most significant 4 bytes from before were all zero, goto loc_2 -- (i.e. input float does not overflow a 32 bit int)
            fstp    dword ptr [ecx] ; Store Real and Pop        // else, dump the fractional portion of the original float over the least significant bytes of the 64bit integer

;// at this point:
;// - the FPU stack should be empty
;// - eax holds a copy of the least significant 4 bytes of the 64bit integer (return value)
;// - edx holds a copy of the most significant 4 bytes of the 64bit integer
;// - [ecx] points to a float representing the part of the input that would be lost in integer truncation
;// - [ecx+4] points at the most significant 4 bytes of our 64bit integer output (probably considered garbage now and not used again)

            mov     ecx, [ecx]      ;                           // make ecx store what it's pointing at directly instead of the pointer to it
            add     esp, 10h        ; Add                       // clean up stack space from the beginning
            xor     ecx, 80000000h  ; Logical Exclusive OR      // mask off the sign bit of the fractional float
            add     ecx, 7FFFFFFFh  ; Add                       // add signed int max (still need to figure out why this)
            adc     eax, 0          ; Add with Carry            // clear carry bit
            retn                    ; Return Near from Procedure
; ---------------------------------------------------------------------------

loc_2:
;// at this point: the FPU stack still holds the fractional (non-integer) portion of the original float that woud have been lost to truncation
            fstp    dword ptr [ecx] ; Store Real and Pop        // store non-integer part as float in local stack space, and remove it from the FPU stack
            mov     ecx, [ecx]      ;                           // make ecx store what it's pointing at directly instead of the pointer to it
            add     esp, 10h        ; Add                       // clean up stack space from the beginning
            add     ecx, 7FFFFFFFh  ; Add                       // add signed int max to the float we just stored (still need to figure out why this)
            sbb     eax, 0          ; Integer Subtraction with Borrow // clear carry bit
            retn                    ; Return Near from Procedure
; ---------------------------------------------------------------------------

loc_3:                             
            test    edx, 7FFFFFFFh  ; Logical Compare           // test the most significant bytes for signed int max
            jnz     short loc_1     ; Jump if Not Zero (ZF=0)   // if the high bytes equal signed int max go back to loc_1
            fstp    dword ptr [ecx] ; Store Real and Pop        // else, empty the FPU stack
            fstp    dword ptr [ecx] ; Store Real and Pop        // empty the FPU stack
            add     esp, 10h        ; Add                       // clean up stack space from the beginning
            retn                    ; Return Near from Procedure