Наиболее эффективный стандартно-совместимый способ интерпретации 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