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:
-
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.
-
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ą.
-
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.
-
Przez
reinterpret_cast
ing dochar*
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?
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.
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).
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?
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
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