Najbardziej efektywny zgodny ze standardami sposób reinterpretacji int jako float

Załóżmy, że mam gwarancje, że float to IEEE 754 binary32. Biorąc pod uwagę wzór bitowy, który odpowiada poprawnemu float, przechowywanemu w std::uint32_t, Jak można go ponownie zinterpretować jako float w najbardziej efektywny sposób zgodny ze standardem?

float reinterpret_as_float(std::uint32_t ui) {
   return /* apply sorcery to ui */;
}

Mam kilka sposobów, które wiem/podejrzewam / zakładam, że mają pewne problemy:

  1. Via reinterpret_cast,

    float reinterpret_as_float(std::uint32_t ui) {
        return reinterpret_cast<float&>(ui);
    }
    

    Lub równoważnie

    float reinterpret_as_float(std::uint32_t ui) {
        return *reinterpret_cast<float*>(&ui);
    }
    

    Który cierpi na problemy z aliasingiem.

  2. Przez union,

    float reinterpret_as_float(std::uint32_t ui) {
        union {
            std::uint32_t ui;
            float f;
        } u = {ui};
        return u.f;
    }
    

    Który nie jest w rzeczywistości legalny, ponieważ jest dozwolony tylko do odczytu z ostatnio napisanego do członka. Wydaje się jednak, że niektóre Kompilatory (gcc) na to pozwalają.

  3. Via std::memcpy,

    float reinterpret_as_float(std::uint32_t ui) {
        float f;
        std::memcpy(&f, &ui, 4);
        return f;
    }
    

    Które AFAIK jest legalne, ale wywołanie funkcji do kopiowania pojedynczego słowa Wydaje się marnotrawne, choć może zostać zoptymalizowane.

  4. Przez reinterpret_cast ing do char* i kopiowanie,

    float reinterpret_as_float(std::uint32_t ui) {
        char* uip = reinterpret_cast<char*>(&ui);
        float f;
        char* fp = reinterpret_cast<char*>(&f);
        for (int i = 0; i < 4; ++i) {
            fp[i] = uip[i];
        }
        return f;
    }
    

    Które AFAIK jest również legalne, ponieważ char wskaźniki są wyłączone z aliasingu problemy i ręczna pętla kopiowania bajtów zapisuje możliwe wywołanie funkcji. Pętla na pewno zostanie rozwinięta, jednak 4 ewentualnie oddzielne jednobajtowe ładunki / sklepy są niepokojące, nie mam pojęcia, czy jest to optymalne do pojedynczego czterobajtowego ładowania / przechowywania.

4 to najlepsze, co udało mi się wymyślić.

Czy jak na razie mam rację? Czy istnieje lepszy sposób, aby to zrobić, zwłaszcza taki, który zagwarantuje pojedynczy ładunek / magazyn?

Author: yuri kilochek, 2013-12-24

4 answers

Afaik, istnieją tylko dwa podejścia, które są zgodne ze ścisłymi regułami aliasingu: memcpy() i cast to char* z kopiowaniem. Wszystkie inne odczytują float z pamięci, która należy do uint32_t, a kompilator może wykonać odczyt przed zapisem do tej pamięci. Może nawet całkowicie zoptymalizować zapis, ponieważ może udowodnić, że przechowywana wartość Nigdy nie będzie używana zgodnie ze ścisłymi regułami aliasingu, co skutkuje zwróceniem wartości śmieci.

To naprawdę zależy od kompilator / optymalizuje czy memcpy() Czy char* kopiowanie jest szybsze. W obu przypadkach inteligentny kompilator może być w stanie zorientować się, że może po prostu załadować i skopiować uint32_t, ale nie ufałbym żadnemu kompilatorowi, aby to zrobił, zanim nie zobaczę tego w wynikowym kodzie asemblera.

Edit:
Po kilku testach z gcc 4.8.1, mogę powiedzieć, że podejście memcpy() jest najlepsze dla tego kompilatora particulare, zobacz poniżej szczegóły.


Kompilowanie

#include <stdint.h>

float foo(uint32_t a) {
    float b;
    char* aPointer = (char*)&a, *bPointer = (char*)&b;
    for( int i = sizeof(a); i--; ) bPointer[i] = aPointer[i];
    return b;
}

Z gcc -S -std=gnu11 -O3 foo.c daje ten kod:

movl    %edi, %ecx
movl    %edi, %edx
movl    %edi, %eax
shrl    $24, %ecx
shrl    $16, %edx
shrw    $8, %ax
movb    %cl, -1(%rsp)
movb    %dl, -2(%rsp)
movb    %al, -3(%rsp)
movb    %dil, -4(%rsp)
movss   -4(%rsp), %xmm0
ret

To nie jest optymalne.

Robienie tego samego z

#include <stdint.h>
#include <string.h>

float foo(uint32_t a) {
    float b;
    char* aPointer = (char*)&a, *bPointer = (char*)&b;
    memcpy(bPointer, aPointer, sizeof(a));
    return b;
}

Plony (ze wszystkimi poziomami optymalizacji z wyjątkiem -O0):

movl    %edi, -4(%rsp)
movss   -4(%rsp), %xmm0
ret

To jest optymalne.

 14
Author: cmaster,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-31 11:50:45

Jeśli bitpattern w zmiennej integer jest taki sam jak poprawna wartość float, wtedy union jest prawdopodobnie najlepszym i najbardziej zgodnym sposobem. I to jest rzeczywiście legalne, jeśli przeczytać specyfikację(nie pamiętam sekcji w tej chwili).

 2
Author: Some programmer dude,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-24 15:06:52

Memcpy jest zawsze bezpieczne, ale zawiera kopię

Casting może prowadzić do problemów

Union-wydaje się być dozwolone w C99 i C11, Nie wiem co do C++

Spójrz na:

Jaka jest ścisła zasada aliasingu?

I

Czy typowanie przez Unię jest nieokreślone w C99, a czy stało się określone w C11?

 2
Author: doron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:53:15
float reinterpret_as_float(std::uint32_t ui) {
   return *((float *)&ui);
}

Jako funkcja prosta, jej kod jest tłumaczony na assembly w następujący sposób (Pelles C Dla Windows):

fld [esp+4]
ret

Jeśli jest zdefiniowana jako inline Funkcja, to kod taki jak ten (n jest unsigned, x jest float):

x = reinterpret_as_float (n);

Jest tłumaczone na asembler w następujący sposób:

fld [ebp-4]  ;RHS of asignment. Read n as float
fstp dword ptr [ebp-8]  ;LHS of asignment
 -2
Author: mcleod_ideafix,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-31 12:08:25