What's a proper way of type-punning a float to

2019-01-04 13:08发布

The code below performs a fast inverse square root operation by some bit hacks. The algorithm was probably developed by Silicon Graphics in early 1990's and it's appeared in Quake 3 too. more info

However I get the following warning from GCC C++ compiler: dereferencing type-punned pointer will break strict-aliasing rules

Should I use static_cast, reinterpret_cast or dynamic_cast instead in such situations?

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

7条回答
Bombasti
2楼-- · 2019-01-04 13:27

Take a look at this for more information on type punning and strict aliasing.

The only safe cast of a type into an array is into a char array. If you want one data address to be switchable to different types you will need to use a union

查看更多
Rolldiameter
3楼-- · 2019-01-04 13:30

I'm adding an answer not to refute the accepted answer, but to augment it. I believe the accepted answer is both correct and efficient (and I've just upvoted it). However I wanted to demonstrate another technique that is just as correct and efficient:

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

Using clang++ with optimization at -O3, I compiled plasmacel's code, R. Martinho Fernandes code, and this code, and compared the assembly line by line. All three were identical.

Update

I no longer believe this answer is correct, due to feedback I've gotten from the committee. But I want to leave it up for informational purposes. And I am purposefully hopeful that this answer can be made correct by the committee (if it chooses to do so). I.e. there's nothing about the underlying hardware that makes this answer incorrect, it is just the judgement of a committee that makes it so, or not so.

查看更多
家丑人穷心不美
4楼-- · 2019-01-04 13:34

There are a few good answers here that address the type-punning issue.

I want to address the "fast inverse square-root" part. Don't use this "trick" on modern processors. Every mainstream vector ISA has a dedicated hardware instruction to give you a fast inverse square-root. Every one of them is both faster and more accurate than this oft-copied little hack.

These instructions are all available via intrinsics, so they are relatively easy to use. In SSE, you want to use rsqrtss (intrinsic: _mm_rsqrt_ss( )); in NEON you want to use vrsqrte (intrinsic: vrsqrte_f32( )); and in AltiVec you want to use frsqrte. Most GPU ISAs have similar instructions. These estimates can be refined using the same Newton iteration, and NEON even has the vrsqrts instruction to do part of the refinement in a single instruction without needing to load constants.

查看更多
我只想做你的唯一
5楼-- · 2019-01-04 13:45

Forget casts. Use 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;

The original code tries to initialize the int32_t by first accessing the float object through an int32_t pointer, which is where the rules are broken. The C-style cast is equivalent to a reinterpret_cast, so changing it to reinterpret_cast would not make much difference.

The important difference when using memcpy is that the bytes are copied from the float into the int32_t, but the float object is never accessed through an int32_t lvalue, because memcpy takes pointers to void and its insides are "magical" and don't break the aliasing rules.

查看更多
兄弟一词,经得起流年.
6楼-- · 2019-01-04 13:47

The cast invokes undefined behaviour. No matter what form of cast you use, it will still be undefined behaviour. It is undefined no matter what type of cast you use.

Most compilers will do what you expect, but gcc likes being mean and is probably going to assume you didn't assign the pointers despite all indication you did and reorder the operation so they give some strange result.

Casting a pointer to incompatible type and dereferencing it is an undefined behaviour. The only exception is casting it to or from char, so the only workaround is using std::memcpy (as per R. Martinho Fernandes' answer). (I am not sure how much it is defined using unions; It does stand a better chance of working though).

That said, you should not use C-style cast in C++. In this case, static_cast would not compile, nor would dynamic_cast, forcing you to use reinterpret_cast and reinterpret_cast is a strong suggestion you might be violating strict aliasing rules.

查看更多
smile是对你的礼貌
7楼-- · 2019-01-04 13:48

Based on the answers here I made a modern "pseudo-cast" function for ease of application.

C99 version (while most compilers support it, theoretically could be undefined behavior in some)

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

Universal versions (based on the accepted answer)

Cast types with the same size:

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

Cast types with any sizes:

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

Use it like:

float f = 3.14f;
uint32_t u = pseudo_cast<uint32_t>(f);
查看更多
登录 后发表回答