Наиболее эффективный стандартно-совместимый способ интерпретации int как float
Предположим, у меня есть гарантии, что float
является IEEE 754 binary32. Учитывая битовый шаблон, соответствующий допустимому float, сохраненному в std::uint32_t
, как это можно интерпретировать как float
в самом эффективном стандартном уступчивом пути?
float reinterpret_as_float(std::uint32_t ui) {
return /* apply sorcery to ui */;
}
у меня есть несколько способов, которые я знаю/подозреваю/предполагаю, есть некоторые вопросы:
-
Via
reinterpret_cast
,float reinterpret_as_float(std::uint32_t ui) { return reinterpret_cast<float&>(ui); }
или
float reinterpret_as_float(std::uint32_t ui) { return *reinterpret_cast<float*>(&ui); }
который страдает от сглаживания проблемы.
-
Via
union
,float reinterpret_as_float(std::uint32_t ui) { union { std::uint32_t ui; float f; } u = {ui}; return u.f; }
что на самом деле не является законным, так как разрешено читать только из самых последних написанных членам. Тем не менее, некоторые компиляторы (gcc) позволяют это.
-
Via
std::memcpy
,float reinterpret_as_float(std::uint32_t ui) { float f; std::memcpy(&f, &ui, 4); return f; }
который AFAIK является законным, но вызов функции для копирования одного слова кажется расточительным, хотя он может быть оптимизирован.
-
Via
reinterpret_cast
наchar*
и копируя,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; }
, который, насколько мне известно, тоже юридическое, как
char
указатели освобождаются от проблем с псевдонимами, а цикл ручного копирования байтов сохраняет возможный вызов функции. Цикл, безусловно, будет развернут, но 4, возможно, отдельные однобайтовые нагрузки/хранилища вызывают беспокойство, я понятия не имею, оптимизируется ли это для одной четырехбайтовой загрузки / хранилища.
на 4
лучшее, что я смог придумать.
я прав до сих пор? Есть ли лучший способ сделать это, в частности, тот, который гарантирует одиночную загрузку/хранение?
4 ответов
Afaik, есть только два подхода, которые соответствуют строгим правилам сглаживания:memcpy()
и приведения к char*
при копировании. Все остальные читают float
из памяти, которая принадлежит uint32_t
, и компилятору разрешено выполнять чтение перед записью в это место памяти. Он может даже оптимизировать запись вообще, поскольку он может доказать, что сохраненное значение никогда не будет использоваться в соответствии со строгими правилами псевдонима, что приводит к возвращению значения мусора.
это действительно зависит от компилятора/оптимизирует ли memcpy()
или char*
копировать быстрее. В обоих случаях интеллектуальный компилятор может понять, что он может просто загрузить и скопировать uint32_t
, но я бы не доверял компилятору делать это, прежде чем я увижу его в результирующем коде ассемблера.
Edit:
После некоторого тестирования с gcc 4.8.1 я могу сказать, что memcpy()
подход является лучшим для этого компилятора particulare, см. ниже подробности.
сборка
#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;
}
С gcc -S -std=gnu11 -O3 foo.c
дает собрать этот код:
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
это не оптимально.
делаешь то же самое с
#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;
}
выходы (со всеми уровнями оптимизации, кроме -O0
):
movl %edi, -4(%rsp)
movss -4(%rsp), %xmm0
ret
этот вариант является оптимальным.
Если bitpattern в целочисленной переменной совпадает с допустимым float
значение, тогда объединение, вероятно, лучший и наиболее совместимый способ пойти. И это на самом деле законно, если Вы читаете спецификацию (не помните раздел на данный момент).
memcpy всегда безопасен, но включает в себя копию
литье может привести к проблемам
union-кажется, разрешено в C99 и C11, не уверен в C++
взгляните на:
что такое строгое правило антиалиасинга?
и
является ли тип-каламбур через соединение неуказанным в C99, и он стал указан в C11?
float reinterpret_as_float(std::uint32_t ui) {
return *((float *)&ui);
}
как простая функция, ее код переводится в сборку следующим образом (Pelles C для Windows):
fld [esp+4]
ret
если он определен как inline
функция, затем такой код (n
будучи неподписанными, x
будучи поплавка):
x = reinterpret_as_float (n);
переводится на ассемблер следующим образом:
fld [ebp-4] ;RHS of asignment. Read n as float
fstp dword ptr [ebp-8] ;LHS of asignment