Jestem w sytuacji, w której muszę obliczyć coś w rodzaju size_t s=(size_t)floorf(f);
. Oznacza to, że argument jest zmiennoprzecinkowy, ale ma on wartość całkowitą (zakładamy, że floorf(f)
jest wystarczająco mały, aby być dokładnie odwzorowanym). Optymalizując to, odkryłem coś interesującego.Co jest tak trudnego w odniesieniu do `uint64_t`? (Konwersja złożenia z `float`)
Poniżej podano niektóre konwersje z float
na liczby całkowite (GCC 5.2.0 -O3). Dla jasności podana konwersja jest wartością zwracaną funkcji testowej.
Oto int32_t x=(int32_t)f
:
cvttss2si eax, xmm0
ret
Oto uint32_t x=(uint32_t)f
:
cvttss2si rax, xmm0
ret
Oto int64_t x=(int64_t)f
:
cvttss2si rax, xmm0
ret
Ostatni, oto uint64_t x=(uint64_t)f;
:
ucomiss xmm0, DWORD PTR .LC2[rip]
jnb .L4
cvttss2si rax, xmm0
ret
.L4:
subss xmm0, DWORD PTR .LC2[rip]
movabs rdx, -9223372036854775808
cvttss2si rax, xmm0
xor rax, rdx
ret
.LC2:
.long 1593835520
Ten ostatni jest znacznie bardziej złożony niż pozostałe. Co więcej, Clang i MSVC zachowują się podobnie. Dla Państwa wygody, mam przetłumaczone go do pseudo-C:
float lc2 = (float)(/* 2^63 - 1 */);
if (f<lc2) {
return (uint64_t)f;
} else {
f -= lc2;
uint64_t temp = (uint64_t)f;
temp ^= /* 2^63 */; //Toggle highest bit
return temp;
}
To wygląda jak stara się obliczyć pierwszy przelewowy mod 64 poprawnie. Wydaje się to dość fałszywe, ponieważ the documentation for cvttss2si mówi mi, że jeśli wystąpi przepełnienie (w 2^32, a nie 2^64), zwracana jest "nieokreślona liczba całkowita (80000000H)".
Moje pytania:
- Co to naprawdę robi i dlaczego?
- Dlaczego coś podobnego nie było zrobione dla innych typów liczb całkowitych?
- Jak zmienić konwersję, aby uzyskać podobny kod (tylko linie wyjściowe 3 i 4) (ponownie, zakładam, że wartość jest dokładnie reprezentowalna)?
Ten wpis na blogu i ten komentarz odnoszą się w szczególności do Twojego pytania: http://blog.frama-c.com/index.php?post/2013/10/09/Overflow-float-integer#c379 –