2015-09-21 13 views
7

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:

  1. Co to naprawdę robi i dlaczego?
  2. Dlaczego coś podobnego nie było zrobione dla innych typów liczb całkowitych?
  3. Jak zmienić konwersję, aby uzyskać podobny kod (tylko linie wyjściowe 3 i 4) (ponownie, zakładam, że wartość jest dokładnie reprezentowalna)?
+0

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 –

Odpowiedz

8

Od cvttss2si ma podpisane konwersja będzie rozważyć numery w przedziale [2^63, 2^64) się być poza zasięgiem, gdy w rzeczywistości są one w przedziale dla unsigned. W związku z tym przypadek ten jest wykrywany i mapowany do niskiej połowy w float, a korekta jest stosowana po konwersji.

Co do pozostałych przypadkach zauważyć, że konwersja uint32_t nadal używa cel 64 bitową, która będzie pracować dla pełnego zakresu obcinania uint32_t i dalszych jest ukryte za pomocą niskich 32 bitów wyniku według konwencji wywołania.

Pod względem uniknięcia dodatkowego kodu, zależy to od tego, czy dane wejście może należeć do wyżej wymienionego zakresu. Jeśli to możliwe, nie da się tego obejść. W przeciwnym razie mógłby zadziałać podwójny rzut pierwszy do podpisania, a następnie do unsigned. (uint64_t)(int64_t)f.