La manière la plus efficace de réinterpréter int en tant que flotteur
supposons que j'ai des garanties que float
est IEEE 754 binary32. Étant donné un patron de bits qui correspond à un flotteur valide, stocké dans std::uint32_t
, comment peut-on le réinterpréter comme un float
d'une manière conforme standard la plus efficace?
float reinterpret_as_float(std::uint32_t ui) {
return /* apply sorcery to ui */;
}
j'ai quelques moyens que je sais / suspecte/suppose ont quelques problèmes:
-
Via
reinterpret_cast
,float reinterpret_as_float(std::uint32_t ui) { return reinterpret_cast<float&>(ui); }
ou l'équivalent
float reinterpret_as_float(std::uint32_t ui) { return *reinterpret_cast<float*>(&ui); }
qui souffre de problèmes d'alias.
-
Via
union
,float reinterpret_as_float(std::uint32_t ui) { union { std::uint32_t ui; float f; } u = {ui}; return u.f; }
qui n'est pas réellement légal, car il est seulement permis de lire à partir de la plus récente écrit à un membre. Pourtant, il semble que certains compilateurs (gcc).
-
Via
std::memcpy
,float reinterpret_as_float(std::uint32_t ui) { float f; std::memcpy(&f, &ui, 4); return f; }
ce qui AFAIK est légal, mais un appel de fonction pour copier le mot simple semble inutile, bien qu'il puisse obtenir optimisé loin.
-
Via
reinterpret_cast
ingchar*
et la copie,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; }
qui AFAIK est également légal, comme
char
pointeurs sont exemptés de problèmes d'aliasing et manuel byte copie boucle enregistre un appel de fonction possible. La boucle sera très certainement déroulée, mais 4 charges d'un octet éventuellement séparées / les magasins sont inquiétants, Je n'ai pas idée si cela est optimisable à quatre byte simple de charge / de stockage.
le 4
est le meilleur que j'ai pu trouver.
ai-je raison jusqu'à présent? Y a-t-il une meilleure façon de le faire, en particulier une façon qui garantira un seul chargement/stockage?
4 réponses
Afaik, il n'y a que deux approches qui sont conformes aux règles d'alias strictes: memcpy()
et moulé à char*
avec copie. Tous les autres lisent un float
de mémoire qui appartient à un uint32_t
, et le compilateur est autorisé à effectuer la lecture avant l'écriture à cet emplacement de mémoire. Il pourrait même optimiser l'écriture tout à fait car il peut prouver que la valeur stockée ne sera jamais utilisé selon des règles d'aliasing strictes, résultant en un retour de déchets valeur.
cela dépend vraiment du compilateur/optimise si la copie memcpy()
ou char*
est plus rapide. Dans les deux cas, un compilateur intelligent pourrait être en mesure de comprendre qu'il peut simplement charger et copier un uint32_t
, mais je ne ferais confiance à aucun compilateur pour le faire avant que je l'ai vu dans le code assembleur résultant.
Edit:
Après quelques tests avec gcc 4.8.1, je peux dire que le memcpy()
approche est la meilleure pour ce compilateur particulier, voir ci-dessous pour plus de détails.
Compilation
#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;
}
avec gcc -S -std=gnu11 -O3 foo.c
donne ce code d'assemblage:
movl %edi, %ecx
movl %edi, %edx
movl %edi, %eax
shrl , %ecx
shrl , %edx
shrw , %ax
movb %cl, -1(%rsp)
movb %dl, -2(%rsp)
movb %al, -3(%rsp)
movb %dil, -4(%rsp)
movss -4(%rsp), %xmm0
ret
ce n'est pas optimal.
Faire la même chose avec
#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;
}
rendements (avec tous les niveaux d'optimisation à l'exception de -O0
):
movl %edi, -4(%rsp)
movss -4(%rsp), %xmm0
ret
c'est optimal.
si le bitpattern dans la variable entière est la même qu'une valeur valide float
, alors l'union est probablement la meilleure et la plus conforme façon de procéder. Et c'est en fait légal si vous lisez les spécifications (ne vous souvenez pas de la section pour le moment).
memcpy est toujours plus sûr, mais ne impliquent un copier
coulée peut conduire à des problèmes de
syndicat-semble être autorisé en C99 et C11, pas sûr de C++
regardez:
Quelle est la stricte règle d'alias?
et
float reinterpret_as_float(std::uint32_t ui) {
return *((float *)&ui);
}
comme simple fonction, son code est traduit en assemblée comme ceci (Pelles C Pour Windows):
fld [esp+4]
ret
Si défini par la fonction inline
, alors un code comme celui-ci ( n
non signé, x
flottant):
x = reinterpret_as_float (n);
est traduit à assembleur comme ceci:
fld [ebp-4] ;RHS of asignment. Read n as float
fstp dword ptr [ebp-8] ;LHS of asignment