What is going on with: int i = *(int*)&x where x is a float?

Started by
21 comments, last by Chris Lomont 16 years, 1 month ago
Ok, I'm sure many have seen this snippet for inverse square roots: float InvSqrt(float x) { float xhalf = 0.5f*x; int i = *(int*)&x // get bits for floating value printf("%x\n", i); i = 0x5f3759df - (i>>1); // gives initial guess y0 x = *(float*)&i // convert bits back to float x = x*(1.5f-xhalf*x*x); // Newton step, repeating increases accuracy return x; } What's happening with this line: int i = *(int*)&x Is that converting float x to a pointer, casting it to an int pointer and then dereferencing it and putting the result in an int? Is that defined? I also see similar code with the *(int*)&var format elsewhere now that I am looking for it.
Advertisement
I suppose you could do this if you wanted be able to access the individual bits of a floating point number, since that's would you would have in the integer. I have no idea how valid this sort of thing is, but it certainly looks terribly ugly and unsafe.
Quote:Original post by MJP
I suppose you could do this if you wanted be able to access the individual bits of a floating point number, since that's would you would have in the integer. I have no idea how valid this sort of thing is, but it certainly looks terribly ugly and unsafe.


Write-up on it:

http://www.lomont.org/Math/Papers/2003/InvSqrt.pdf

But he focuses on the constant a few lines down and the overall efficiency gains.

I'm just hung up on the syntax of that one line.
It is of course designed for 4 byte integers and single precision floating point values. So it's not going to be covered by the C++ standard.

It looks ugly, but all it does is place the exact same 32 bits that came from a float into what is now interpreted as an integer.

This is so the bitshift and bit manipulation can be performed.

Of course, the constant 0x5f3759df is also dependent on 32 bit integers and IEEE 754.
Two little notes:

1) That code can break if you run on a compiler that supports strict aliasing optimizations if they are enabled. Casting through a union is the most common workaround. See this page for more details: http://www.cellperformance.com/mike_acton/2006/06/understanding_strict_aliasing.html

2) The compiler/CPU can do screwy stuff when converting between an integer and float if you set the wrong bits. We had a bug in our endian swapping code a while back where converting an int to a float via address casting caused NaNs in very specific cases. I don't recall the specifics, but it went all the way down to getting the value into a register. I think it was doing something like:

float fData;
Read(&fData, sizeof(4)); // Reads in unswapped data, which involves interpreting the data as an int.
Swap(&fData, sizeof(4)); // Swaps the data to the correct endian

If the unswapped data bits were arranged just right, the value stored in fData was converted to a NaN, basically corrupting the data before the Swap occured.

Basically, we needed to do this instead:

uint32 nData;
Read(&nData, sizeof(4)); // Reads in unswapped data
Swap(&nData, sizeof(4)); // Swaps the data to the correct endian
float fData = UnionCast<float>(nData); // Convert the representation to a float

Moral: Be really careful when converting between types. Even when you know what you are doing, its very easy to introduce either a compiler specific bug or a value specific bug.
Quote:Is that converting float x to a pointer, casting it to an int pointer and then dereferencing it and putting the result in an int?


float pi = 3.14159;
int x = (int)pi;
-> x == 3 (00000003h)

int x = *(int*)π
-> x is a bitwise copy of pi, so you can perform bitwise operations. It most certainly is not 00000003h.
Long story short, accessing a memory address through a type-punned pointer (other than char*) gives undefined results according to the standard. In practice, this works as expected on most common platforms, though it will break strict aliasing as pointed out above. "As expected" means that it gives you the bit-pattern of the floating point number, which wouldn't be possible to get through normal means.

Most compilers support an extension to allow you do the same thing via unions:
union { float f; unsigned int i; } converter;converter.f = someFloat;someInt = converter.i;
I actually prefer this method quite a bit, but it too isn't fully supported by the standard. It's a very common compiler extension though, so in practice it's safe to use.

The only standards-compliant way to do this conversion is by casting the float to a char* and reconstructing it into an int one byte at a time:
compile_time_assert(sizeof(unsigned int) == sizeof(float)); // not guaranteedunsigned char* src = (unsigned char*)&someFloatunsigned char* dest = (unsigned char*)&someIntfor (int i = 0; i < sizeof(float); i++){  dest = src;}
But this code is overbearing for the sake of ultra-correctness when the other two methods work with most known combinations of platform and compiler manufacturer.

(Type-punning == referencing a memory address by something other than its actual type.)

As stated, I'd prefer the union method to the in-place pointer casting, but if you really need to do this I suppose it's up to you which you choose.
Quote:Original post by exwonder
Most compilers support an extension to allow you do the same thing via unions:
union { float f; unsigned int i; } converter;converter.f = someFloat;someInt = converter.i;
I actually prefer this method quite a bit, but it too isn't fully supported by the standard. It's a very common compiler extension though, so in practice it's safe to use.

I would recommend casting over converting via a union since the former is permitted by the Standard (albeit with implementation-defined behaviour) and the latter is prohibited, even if it does work in practice. A cast is also clearer, especially if you use the form reinterpret_cast< int & >(x).

Σnigma
Quote:Original post by Enigma
I would recommend casting over converting via a union since the former is permitted by the Standard (albeit with implementation-defined behaviour) and the latter is prohibited, even if it does work in practice. A cast is also clearer, especially if you use the form reinterpret_cast< int & >(x).

I'll have to re-look up the union thing at some point.

reinterpret_cast is definitely a good solution, though, if I recall correctly... reinterpret_cast's from int to float are not allowed. So it won't be helpful in every situation where you'd want to manipulate the bits of a float directly.

As messy as it is, the C-style casting captures the fact that the conversion will most likely have to go between register sets via memory to happen, which is a nice side-effect, though one that most people won't be thinking about.
I don't have a C compiler at hand, but wouldn't memcpy work?

memcpy(&i, &f, sizeof(f));

This topic is closed to new replies.

Advertisement