27

下面的代码通过一些位黑客来执行快速的平方根逆运算。该算法可能是由 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;
}
4

8 回答 8

38

忘记演员表。使用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 风格的演员表等效于 a reinterpret_cast,因此将其更改为reinterpret_cast不会有太大的区别。

使用 memcpy 时的重要区别是字节从 复制floatint32_t,但float对象永远不会通过int32_t左值访问,因为memcpy它需要指向 void 的指针,并且它的内部是“神奇的”并且不会破坏别名规则。

于 2013-07-22T14:23:02.497 回答
13

这里有一些很好的答案可以解决类型双关语问题。

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

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

于 2013-08-11T01:46:53.773 回答
7

更新

由于我从委员会得到的反馈,我不再相信这个答案是正确的。但我想保留它以供参考。我有目的地希望委员会能够纠正这个答案(如果它选择这样做的话)。即,底层硬件并没有使这个答案不正确,只是委员会的判断使它如此或不是这样。


我添加一个答案不是为了反驳接受的答案,而是为了增加它。我相信公认的答案既正确又有效(我刚刚投了赞成票)。但是我想展示另一种同样正确和有效的技术:

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 的代码和这段代码,并逐行比较了汇编代码。这三个都是一样的。这是由于编译器选择像这样编译它。编译器产生不同的、损坏的代码同样有效。

于 2013-07-23T00:04:45.530 回答
4

如果您有权访问 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_cast只有MSVC 支持在 Godbolt 上查看演示

在等待实现的同时,如果你使用 Clang,你可以尝试__builtin_bit_cast. 像这样改变演员阵容

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

演示

于 2020-11-14T08:14:44.440 回答
1

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

大多数编译器会按照您的期望进行,但是 gcc 喜欢刻薄,并且可能会假设您没有分配指针,尽管您做了所有指示并重新排序操作,因此它们会给出一些奇怪的结果。

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

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

于 2013-07-22T14:26:39.723 回答
1

查看内容以获取有关类型双关语和严格别名的更多信息。

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

于 2013-07-22T14:39:53.840 回答
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);

C++20 更新

C++20constexpr std::bit_cast在标头<bit>中引入了对于具有相同大小的类型在功能上等效的功能。尽管如此,如果您想自己实现此功能(假设constexpr不是必需的),或者如果您想支持不同大小的类型,上述版本仍然有用。

于 2018-07-12T09:13:44.663 回答
0

唯一可以在这里工作的演员是reinterpret_cast. (即便如此,至少有一个编译器会竭尽全力确保它不会工作。)

但你实际上想做什么?当然有一个更好的解决方案,它不涉及类型双关语。类型双关语适用的情况非常非常少,并且它们都在非常非常低级的代码中,例如序列化或实现 C 标准库(例如函数,如 modf)。否则(甚至可能在序列化中),类似ldexpand的函数modf可能会更好地工作,并且肯定更具可读性。

于 2013-07-22T14:27:15.263 回答