什么是将一个浮点数类型化为int的正确方法,反之亦然?

时间:2013-07-22 14:18:08

标签: c++ undefined-behavior gcc-warning strict-aliasing type-punning

下面的代码通过一些攻击执行快速反平方根操作。 该算法可能是由Silicon Graphics在1990年代早期开发的,它也出现在Quake 3中。 more info

但是我从GCC C ++编译器得到以下警告:解除引用类型惩罚指针会破坏严格别名规则

在这种情况下,我应该使用static_castreinterpret_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;
}

8 个答案:

答案 0 :(得分:34)

忘记演员表。使用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指向void并且其内部是“神奇的”并且不会破坏别名规则。

答案 1 :(得分:12)

这里有一些很好的答案可以解决打字问题。

我想解决“快速反平方根”部分。不要在现代处理器上使用这个“技巧”。每个主流矢量ISA都有一个专用的硬件指令,为您提供快速的反平方根。它们中的每一个都比这个经常复制的小黑客更快,更准确。

这些说明都可以通过内在函数获得,因此它们相对容易使用。在SSE中,您想要使用rsqrtss(内在:_mm_rsqrt_ss( ));在NEON中你想使用vrsqrte(内在:vrsqrte_f32( ));在AltiVec中,您想要使用frsqrte。大多数GPU ISA具有类似的指令。这些估计可以使用相同的牛顿迭代进行细化,NEON甚至可以使用vrsqrts指令在单个指令中完成部分细化,而无需加载常量。

答案 2 :(得分:4)

<强>更新

由于我从委员会得到的反馈,我不再相信这个答案是正确的 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;
}

在-O3使用clang ++和优化,我编译了plasmacel的代码,R. Martinho Fernandes代码和这段代码,并逐行比较了装配。这三个都是相同的。 这是由于编译器选择像这样编译它。编译器生成不同的破解代码同样有效。

答案 3 :(得分:1)

演员表调用未定义的行为。无论你使用什么形式的演员,它仍然是未定义的行为。无论你使用什么类型的演员表,它都是未定义的。

大多数编译器都会按照你的期望做,但gcc喜欢它的意思,并且可能会假设你没有指定指针,尽管你做了所有的指示并重新排序操作,所以它们给出了一些奇怪的结果。

将指针转换为不兼容的类型并取消引用它是一种未定义的行为。唯一的例外是将它转换为char或来自char,因此唯一的解决方法是使用std::memcpy(根据R. Martinho Fernandes的回答)。 (我不确定使用工会定义了多少;它确实有更好的工作机会)。

那就是说,你不应该在C ++中使用C风格的强制转换。在这种情况下,static_cast无法编译,dynamic_cast也不会强制您使用reinterpret_castreinterpret_cast强烈建议您可能违反严格的别名规则。

答案 4 :(得分:1)

这里唯一可以使用的演员是reinterpret_cast。 (和 即便如此,至少有一个编译器会不断前进 确保它不起作用。)

但你究竟想做什么?当然可以 一个更好的解决方案,不涉及类型惩罚。有 极少数情况下,类型惩罚是合适的,他们 所有都是非常非常低级的代码,比如序列化, 或实现C标准库(例如 modf)。否则(甚至可能在序列化中),功能 像ldexpmodf可能会更好,当然也可以 更具可读性

答案 5 :(得分:1)

请查看this以获取有关类型惩罚和严格别名的更多信息。

类型中唯一安全的数组转换为char数组。如果您希望将一个数据地址切换为不同类型,则需要使用union

答案 6 :(得分:1)

如果您有权使用C ++ 20或更高版本,则可以使用std::bit_cast

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

目前std::bit_castonly supported by MSVC。参见demo on Godbolt

在等待实现时,如果您使用的是Clang,则可以尝试__builtin_bit_cast。像这样改变演员表

int32_t i = __builtin_bit_cast(std::int32_t, x);
x = __builtin_bit_cast(float, i);

Demo

答案 7 :(得分:0)

基于此处的答案,我制作了现代的“伪广播”功能以简化应用程序。

C99版本  (虽然大多数编译器都支持它,但从理论上讲,某些行为可能是未定义的行为)

this.storage.store

通用版本  (基于接受的答案)

尺寸相同的广播类型:

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;
}