Портирование старой функции компилятора ftol (float to long) на C

Я полагаюсь на старую реализацию, которая делает некоторые вычисления и конвертирует floatк int,

Однако после повторения расчетов некоторые значения отключаются из-за разных результатов округления.

Это сводится к тому, что двоичный файл использует следующий код для преобразования float в 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

Это ведет себя иначе, чем просто делает без знака int var = (unsigned int)floatVal;,

Я считаю, что это старый ftol реализация и было сделано, потому что преобразование из float в int был очень медленным, и компилятору нужно было изменить режим округления FPU.

Это выглядит очень похоже на этот http://www.libsdl.org/release/SDL-1.2.15/src/stdlib/SDL_stdlib.c

Может кто-нибудь помочь мне в преобразовании функции в C? Или скажите мне, как я могу создать встроенную функцию ASM с float параметр и возврат int используя Visual Studio. Тот в SDL_sdtlib.c не имеет заголовка, и я не уверен, как вызвать его без аргументов функции.

3

Решение

Это не совсем отвечает на заданные вопросы, но я хотел начать с более тщательного перевода на английский. Это не идеально, и есть несколько строк, где я все еще пытаюсь отследить намерение. Всем пожалуйста высказывайтесь с вопросами и исправлениями.

            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
2

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]