Подтвердить что ты не робот

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

Ответ 1

Забыть броски. Используйте 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 lvalue, потому что memcpy принимает указатели на void и его внутренности являются "волшебными" и не нарушают правил псевдонимов.

Ответ 2

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

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

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

Ответ 3

Обновление

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


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

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, я скомпилировал код плазмосела, код Р. Мартиньо Фернандеса и этот код и сравнил сборку построчно. Все три были идентичны. Это связано с выбором компилятора для его компиляции следующим образом. Для компилятора было одинаково допустимо создавать другой, неработающий код.

Ответ 4

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

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

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

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

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

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

Версия 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);