-
29-09-2019 - |
Вопрос
Я пытаюсь извлечь биты из поплавка, не вызывая неопределенного поведения. Вот моя первая попытка:
unsigned foo(float x)
{
unsigned* u = (unsigned*)&x;
return *u;
}
Насколько я понимаю, это не гарантированно будет работать из -за строгих правил псевдонимы, верно? Это работает, если сделать промежуточный шаг с указателем персонажа?
unsigned bar(float x)
{
char* c = (char*)&x;
unsigned* u = (unsigned*)c;
return *u;
}
Или мне нужно извлечь индивидуальные байты сам?
unsigned baz(float x)
{
unsigned char* c = (unsigned char*)&x;
return c[0] | c[1] << 8 | c[2] << 16 | c[3] << 24;
}
Конечно, это имеет недостаток в зависимости от эндсианности, но я мог бы жить с этим.
Хак Юнион определенно неопределенный поведение, верно?
unsigned uni(float x)
{
union { float f; unsigned u; };
f = x;
return u;
}
Просто для полноты, вот справочная версия foo
. Анкет Также неопределенное поведение, верно?
unsigned ref(float x)
{
return (unsigned&)x;
}
Итак, возможно ли извлечь биты из поплавка (Предполагая, что оба имеют ширину 32 бита, конечно)?
РЕДАКТИРОВАТЬ: И вот memcpy
Версия, предложенная Гоз. Поскольку многие компиляторы не поддерживают static_assert
Тем не менее, я заменил static_assert
С некоторым шаблоном метапрограммирования:
template <bool, typename T>
struct requirement;
template <typename T>
struct requirement<true, T>
{
typedef T type;
};
unsigned bits(float x)
{
requirement<sizeof(unsigned)==sizeof(float), unsigned>::type u;
memcpy(&u, &x, sizeof u);
return u;
}
Решение
О единственном способе по -настоящему избежать любых проблем - это Memcpy.
unsigned int FloatToInt( float f )
{
static_assert( sizeof( float ) == sizeof( unsigned int ), "Sizes must match" );
unsigned int ret;
memcpy( &ret, &f, sizeof( float ) );
return ret;
}
Поскольку вы Memcpying фиксированная сумма, компилятор оптимизирует его.
Тем не менее, метод профсоюза очень широко поддерживается.
Другие советы
Хак Юнион определенно неопределенный поведение, верно?
Да и нет. Согласно стандарту, это определенно неопределенное поведение. Но это такой широко используемый трюк, что GCC и MSVC и, насколько я знаю, любой другой популярный компилятор, явно гарантирует, что он безопасен и будет работать так, как ожидалось.
Следующее не нарушает правило псевдонизации, потому что оно не использует LValues, получающие доступ к разным типам.
template<typename B, typename A>
B noalias_cast(A a) {
union N {
A a;
B b;
N(A a):a(a) { }
};
return N(a).b;
}
unsigned bar(float x) {
return noalias_cast<unsigned>(x);
}
Если вы действительно хотите быть агностическими по поводу размера типа поплавка и просто верните необработанные биты, сделайте что -то вроде этого:
void float_to_bytes(char *buffer, float f) {
union {
float x;
char b[sizeof(float)];
};
x = f;
memcpy(buffer, b, sizeof(float));
}
Тогда назовите это так:
float a = 12345.6789;
char buffer[sizeof(float)];
float_to_bytes(buffer, a);
Этот метод, конечно, будет производить выход, специфичный для заказа байта вашей машины.