Каков правильный способ ввода чисел – поплавок в 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; } 

    Забудьте броски. Используйте 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 указателя int32_t , в котором нарушены правила. Сценарий в стиле C эквивалентен reinterpret_cast , поэтому его изменение на reinterpret_cast не будет иметь большого значения.

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

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

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

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

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

     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, я скомпилировал код плазмаселя, код R. Martinho Fernandes и этот код, и сравнил сборку по строкам. Все трое были одинаковыми.

    Обновить

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

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

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

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

    Единственный безопасный листинг типа в массив – это массив char . Если вы хотите, чтобы один адрес данных переключался на разные типы, вам необходимо использовать union

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

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

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

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

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

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

     template  inline T pseudo_cast(const U &x) { static_assert(std::is_trivially_copyable::value && std::is_trivially_copyable::value, "pseudo_cast can't handle types which are not trivially copyable"); union { U from; T to; } __x = {x}; return __x.to; } 

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

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

     #include  template  inline T pseudo_cast(const U &x) { static_assert(std::is_trivially_copyable::value && std::is_trivially_copyable::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  template  inline T pseudo_cast(const U &x) { static_assert(std::is_trivially_copyable::value && std::is_trivially_copyable::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(f); 
    Давайте будем гением компьютера.