Как правильно набирать тип с плавающей точкой для типа int и наоборот?

Код ниже выполняет быструю операцию обратного квадратного корня с помощью некоторых битовых хаков.
Алгоритм, вероятно, был разработан Silicon Graphics в начале 1990-х годов и появился в Quake 3.
больше информации

Однако я получаю следующее предупреждение от компилятора GCC C ++: разыменовываемый указатель типа будет нарушен строгий ступенчатости правила

Должен ли я использовать static_cast, reinterpret_cast или же dynamic_cast вместо этого в таких ситуациях?

float InverseSquareRoot(float x)
{
float xhalf = 0.5f*x;
int32_t i = *(int32_t*)&x;
i = 0x5f3759df - (i>>1);
x = *(float*)&i;
x = x*(1.5f - xhalf*x*x);
return x;
}

19

Решение

Забудьте забросы. использование memcpy,

float xhalf = 0.5f*x;
uint32_t i;
assert(sizeof(x) == sizeof(i));
std::memcpy(&i, &x, sizeof(i));
i = 0x5f375a86 - (i>>1);
std::memcpy(&x, &i, sizeof(i));
x = x*(1.5f - xhalf*x*x);
return x;

Исходный код пытается инициализировать int32_t первым доступом к float возражать через int32_t указатель, где правила нарушены. Приведение в стиле C эквивалентно reinterpret_castтак что меняя его на reinterpret_cast не будет иметь большого значения.

Важное отличие при использовании memcpy заключается в том, что байты копируются из float в int32_t, но float объект никогда не доступен через int32_t потому что memcpy делает указатели недействительными, а их внутренности «магическими» и не нарушают правил наложения имен.

30

Другие решения

Здесь есть несколько хороших ответов, которые касаются проблемы типа.

Я хочу обратиться к части «быстрый обратный квадратный корень». Не используйте этот «трюк» на современных процессорах. У каждого основного вектора ISA есть специальная аппаратная инструкция, чтобы дать вам быстрый обратный квадратный корень. Каждый из них быстрее и точнее, чем этот часто копируемый небольшой взлом.

Эти инструкции доступны через встроенные функции, поэтому они относительно просты в использовании. В SSE вы хотите использовать rsqrtss (Внутренняя: _mm_rsqrt_ss( )); в NEON вы хотите использовать vrsqrte (Внутренняя: vrsqrte_f32( )); а в AltiVec вы хотите использовать frsqrte, Большинство графических процессоров имеют аналогичные инструкции. Эти оценки могут быть уточнены с использованием той же итерации Ньютона, а NEON даже имеет vrsqrts инструкция выполнять часть уточнения в одной инструкции без необходимости загружать константы.

8

Обновить

Я больше не верю, что этот ответ правильныйт, из-за обратной связи, которую я получил от комитета. Но я хочу оставить это в ознакомительных целях. И я намеренно надеюсь, что этот ответ может быть исправлен комитетом (если он решит это сделать). То есть нет ничего о базовом оборудовании, которое делает этот ответ неправильным, это просто решение комитета, которое делает это так или нет.


Я добавляю ответ, чтобы не опровергнуть принятый ответ, а дополнить его. Я считаю, что принятый ответ является и правильным, и эффективным (и я только что проголосовал за него). Однако я хотел продемонстрировать другую технику, которая была бы такой же правильной и эффективной:

float InverseSquareRoot(float x)
{
union
{
float as_float;
int32_t as_int;
};
float xhalf = 0.5f*x;
as_float = x;
as_int = 0x5f3759df - (as_int>>1);
as_float = as_float*(1.5f - xhalf*as_float*as_float);
return as_float;
}

Используя clang ++ с оптимизацией при -O3, я скомпилировал код plasmacel, код R. Martinho Fernandes и этот код и сравнил сборку построчно. Все три были идентичны. Это связано с выбором компилятора для его компиляции следующим образом. Для компилятора было одинаково правильно создавать другой, неработающий код.

3

Единственный актерский состав, который будет работать здесь, это reinterpret_cast, (А также
даже тогда, по крайней мере, один компилятор изо всех сил
убедитесь, что это не сработает.)

Но что вы на самом деле пытаетесь сделать? Там конечно
лучшее решение, которое не включает в себя наказание типов. Есть
очень, очень мало случаев, когда уместно печатать, и они
все в очень, очень низкоуровневом коде, такие как сериализация,
или реализации стандартной библиотеки C (например, такие функции, как
modf). В противном случае (и, возможно, даже в сериализации), функции
лайк ldexp а также modf вероятно, будет работать лучше, и, конечно,
быть более читабельным.

1

Взгляни на этот для получения дополнительной информации о типе наказания и строгого алиасинга.

Единственное безопасное приведение типа в массив — в char массив. Если вы хотите, чтобы один адрес данных можно было переключать на разные типы, вам нужно будет использовать union

1

Приведение вызывает неопределенное поведение. Независимо от того, какую форму вы используете, это будет неопределенное поведение. Это не определено независимо от того, какой тип каста вы используете.

Большинство компиляторов будут делать то, что вы ожидаете, но gcc любит быть злым и, вероятно, будет предполагать, что вы не назначали указатели, несмотря на все сделанные вами указания, и переупорядочивать операцию, чтобы они давали какой-то странный результат.

Приведение указателя на несовместимый тип и разыменование его является неопределенным поведением. Единственным исключением является приведение его к типу char или обратно, поэтому единственным обходным решением является использование std::memcpy (согласно ответу Р. Мартиньо Фернандеса). (Я не уверен, насколько это определено с помощью союзов; хотя у него больше шансов на работу).

Тем не менее, вы не должны использовать приведение в стиле C в C ++. В этом случае, static_cast не будет компилироваться, и не будет dynamic_cast, заставляя вас использовать reinterpret_cast а также reinterpret_cast Это сильное предположение, что вы можете нарушать строгие правила псевдонимов.

1

Основываясь на ответах, я сделал современную функцию «псевдо-приведения» для простоты применения.

Версия C99
(хотя большинство компиляторов его поддерживают, теоретически в некоторых случаях поведение может быть неопределенным)

template <typename T, typename U>
inline T pseudo_cast(const U &x)
{
static_assert(std::is_trivially_copyable<T>::value && std::is_trivially_copyable<U>::value, "pseudo_cast can't handle types which are not trivially copyable");

union { U from; T to; } __x = {x};
return __x.to;
}

Универсальные версии
(на основании принятого ответа)

Типы бросков с одинаковыми размерами:

#include <cstring>

template <typename T, typename U>
inline T pseudo_cast(const U &x)
{
static_assert(std::is_trivially_copyable<T>::value && std::is_trivially_copyable<U>::value, "pseudo_cast can't handle types which are not trivially copyable");
static_assert(sizeof(T) == sizeof(U), "pseudo_cast can't handle types with different size");

T to;
std::memcpy(&to, &x, sizeof(T));
return to;
}

Типы отливок любых размеров:

#include <cstring>

template <typename T, typename U>
inline T pseudo_cast(const U &x)
{
static_assert(std::is_trivially_copyable<T>::value && std::is_trivially_copyable<U>::value, "pseudo_cast can't handle types which are not trivially copyable");

T to = T(0);
std::memcpy(&to, &x, (sizeof(T) < sizeof(U)) ? sizeof(T) : sizeof(U));
return to;
}

Используйте это как:

float f = 3.14f;
uint32_t u = pseudo_cast<uint32_t>(f);
0
По вопросам рекламы [email protected]