46

Can anyone explain in a simple way the codes below:

public unsafe static float sample(){    
      int result = 154 + (153 << 8) + (25 << 16) + (64 << 24);

      return *(float*)(&result); //don't know what for... please explain
}

Note: the above code uses unsafe function

For the above code, I'm having hard time because I don't understand what's the difference between its return value compare to the return value below:

return (float)(result);

Is it necessary to use unsafe function if your returning *(float*)(&result)?

ruakh
  • 175,680
  • 26
  • 273
  • 307
gchimuel
  • 527
  • 1
  • 5
  • 9
  • Where did you find the sample? Did it not have any explanation about what it's meant to be returning? – Damien_The_Unbeliever Oct 04 '12 at 08:06
  • it's wrong..`result` is not a pointer..u cant convert it's address to float pointer i guess – Anirudha Oct 04 '12 at 08:07
  • 1
    I find the sample code using the ILSpy in the mscorlib/System/BitConvert/ToSingle. No explaination was given. I need to understand what's the flow because I need to convert it to PHP. – gchimuel Oct 04 '12 at 08:09
  • The the last line: yes, you can only use `*` and `&` if you have the `unsafe` modifier. – Marc Gravell Oct 04 '12 at 08:11
  • why is it that the return of unsafer modifier is different than normal return which doesn't have * and &. I've compare two return of both a function using unsafe modifier and a normal one. Why? – gchimuel Oct 04 '12 at 08:14
  • the `return` is identical - it is just a `float`. You could imagine an extra line in there: `float tmp = *(float*)(&result); return tmp;`. Any difference has nothing whatsoever to do with the `return`. – Marc Gravell Oct 04 '12 at 08:17
  • 10
    Re the difference between `return (float)result;` - that is a *conversion* - it converts the integer `123` to the floating point `123.0F` - but that is **not** a re-interpretive cast; the bytes for `123` and `123.0F` are **completely different**. The *re-interpretive* cast simply says "here's 4 bytes; now treat them as a `float`" – Marc Gravell Oct 04 '12 at 08:20
  • Note that the method does not depend on any inputs, so all calls to it can be replaced with a constant (2.4f as explained by Martin). – OrangeDog Oct 04 '12 at 08:49
  • 1
    Reinterpreting data using pointers in this way is called "type punning". – OrangeDog Oct 04 '12 at 08:53
  • 1
    I'm kind of curious if this is the only way to cast 4 bytes to a float. I know it is in C, but pointer stuff is much more common there as well – Earlz Oct 04 '12 at 14:23
  • @earlz You can do it without using unsafe code by using a struct with explicit layout that places int and float values at the same memory location: http://stackoverflow.com/a/5981382/85661 – Dan Is Fiddling By Firelight Oct 04 '12 at 15:08
  • 3
    @gchimuel To do this in PHP, see http://stackoverflow.com/questions/2624869/bytes-convert-to-float-php rather than trying to reverse engineer what a language that works completely differently does. If you already have a byte array, just use unpack. – Random832 Oct 04 '12 at 15:59

6 Answers6

77

On .NET a float is represented using an IEEE binary32 single precision floating number stored using 32 bits. Apparently the code constructs this number by assembling the bits into an int and then casts it to a float using unsafe. The cast is what in C++ terms is called a reinterpret_cast where no conversion is done when the cast is performed - the bits are just reinterpreted as a new type.

IEEE single precision floating number

The number assembled is 4019999A in hexadecimal or 01000000 00011001 10011001 10011010 in binary:

  • The sign bit is 0 (it is a positive number).
  • The exponent bits are 10000000 (or 128) resulting in the exponent 128 - 127 = 1 (the fraction is multiplied by 2^1 = 2).
  • The fraction bits are 00110011001100110011010 which, if nothing else, almost have a recognizable pattern of zeros and ones.

The float returned has the exact same bits as 2.4 converted to floating point and the entire function can simply be replaced by the literal 2.4f.

The final zero that sort of "breaks the bit pattern" of the fraction is there perhaps to make the float match something that can be written using a floating point literal?


So what is the difference between a regular cast and this weird "unsafe cast"?

Assume the following code:

int result = 0x4019999A // 1075419546
float normalCast = (float) result;
float unsafeCast = *(float*) &result; // Only possible in an unsafe context

The first cast takes the integer 1075419546 and converts it to its floating point representation, e.g. 1075419546f. This involves computing the sign, exponent and fraction bits required to represent the original integer as a floating point number. This is a non-trivial computation that has to be done.

The second cast is more sinister (and can only be performed in an unsafe context). The &result takes the address of result returning a pointer to the location where the integer 1075419546 is stored. The pointer dereferencing operator * can then be used to retrieve the value pointed to by the pointer. Using *&result will retrieve the integer stored at the location however by first casting the pointer to a float* (a pointer to a float) a float is instead retrieved from the memory location resulting in the float 2.4f being assigned to unsafeCast. So the narrative of *(float*) &result is give me a pointer to result and assume the pointer is pointer to a float and retrieve the value pointed to by the pointer.

As opposed to the first cast the second cast doesn't require any computations. It just shoves the 32 bit stored in result into unsafeCast (which fortunately also is 32 bit).

In general performing a cast like that can fail in many ways but by using unsafe you are telling the compiler that you know what you are doing.

Community
  • 1
  • 1
Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
  • "The final zero that sort of "breaks the bit pattern" of the fraction is there perhaps to make the float have an exact decimal representation?" not exact per se - think how in decimal we round 2/3 to 0.66667 not 0.66666 – Random832 Oct 04 '12 at 13:07
  • 1
    +! for using the word "sinister" – Nahum Oct 04 '12 at 13:22
  • @Random832: You are correct that 2.4 cannot be represented exactly using a 32 bit float point number (I didn't realize that initially), but there exist numbers that are not endless fractions in both the decimal and the binary numbering system. E.g. 11/4 = 2.75 decimal = 10.11 binary. – Martin Liversage Oct 04 '12 at 14:08
  • @MartinLiversage I was just explaining why it did ..999A instead of ..9999 - same reason for the 7 instead of 6 at the end of 2/3 in decimal. – Random832 Oct 04 '12 at 14:19
  • @Random832: Sorry, misunderstood, but the example seems so contrived that I'm speculating that someone crafted a random floating point number by using a bit pattern and then adjusted the last digit to make it possible to roundtrip to a floating point literal. – Martin Liversage Oct 04 '12 at 14:28
  • @MartinLiversage haha nice answer. you beat me to it! I knew exactly what the answer was as soon as I saw the title of the question (good question title BTW), but I wasn't awake at the hour that this question was first asked! Good answer. – Trevor Boyd Smith Oct 04 '12 at 20:05
  • I don't know if it's a good thing I knew the answer immediately... or a bad thing... lol. (in the past i have needed to take raw bytes and reinterp them as float value!) – Trevor Boyd Smith Oct 04 '12 at 20:06
19

If i'm interpreting what the method is doing correctly, this is a safe equivalent:

public static float sample() {    
   int result = 154 + (153 << 8) + (25 << 16) + (64 << 24);

   byte[] data = BitConverter.GetBytes(result);
   return BitConverter.ToSingle(data, 0);
}

As has been said already, it is re-interpreting the int value as a float.

Bradley Smith
  • 13,353
  • 4
  • 44
  • 57
  • 2
    Smart conversion! I wonder how the performance of the safe version is compared to the unsafe version. – Rune Grimstad Oct 04 '12 at 08:14
  • 2
    @RuneGrimstad Probably not so great, considering it's copying the data instead of just casting to a pointer of a different type. Still, it's probably the simplest way to do it without using unsafe code. – Bradley Smith Oct 04 '12 at 08:15
  • Agreed. If may also turn out that just doing float calculations directly would be faster. Still a really nice solution! – Rune Grimstad Oct 04 '12 at 08:17
  • @Rune it also involves an extra `byte[]` allocation if you use `GetBytes()` – Marc Gravell Oct 04 '12 at 08:18
  • 2
    It's shame that an intermediate `byte[]` is needed. There is a `BitConverter.Int64BitsToDouble()` method which does the same thing with a `long` and a `double`, but no 'Int32BitsToSingle()' method... – Bradley Smith Oct 04 '12 at 08:21
  • 1
    You can also do this "safely" using "unions" i.e. by using `StructLayout(LayoutKind.Explicit)`. – Mark Hurd Oct 10 '12 at 09:09
  • I should say, you can't safely "union" to a `byte[]`, but you can list the specific bytes. – Mark Hurd Oct 10 '12 at 09:35
  • @MarkHurd Can you tell more about this? – Display Name Sep 24 '13 at 16:17
4

This looks like an optimization attempt. Instead of doing floating point calculations you are doing integer calculations on the Integer representation of a floating point number.

Remember, floats are stored as binary values just like ints.

After the calculation is done you are using pointers and casting to convert the integer into the float value.

This is not the same as casting the value to a float. That will turn the int value 1 into the float 1.0. In this case you turn the int value into the floating point number described by the binary value stored in the int.

It's quite hard to explain properly. I will look for an example. :-)

Edit: Look here: http://en.wikipedia.org/wiki/Fast_inverse_square_root

Your code is basically doing the same as described in this article.

Rune Grimstad
  • 35,612
  • 10
  • 61
  • 76
2

Re : What is it doing?

It is taking the value of the bytes stored int and instead interpreting these bytes as a float (without conversion).

Fortunately, floats and ints have the same data size of 4 bytes.

StuartLC
  • 104,537
  • 17
  • 209
  • 285
2

Because Sarge Borsch asked, here's the 'Union' equivalent:

[StructLayout(LayoutKind.Explicit)]
struct ByteFloatUnion {
  [FieldOffset(0)] internal byte byte0;
  [FieldOffset(1)] internal byte byte1;
  [FieldOffset(2)] internal byte byte2;
  [FieldOffset(3)] internal byte byte3;
  [FieldOffset(0)] internal float single;
}

public static float sample() {
   ByteFloatUnion result;
   result.single = 0f;
   result.byte0 = 154;
   result.byte1 = 153;
   result.byte2 = 25;
   result.byte3 = 64;

   return result.single;
}
Mark Hurd
  • 10,665
  • 10
  • 68
  • 101
0

As others have already described, it's treating the bytes of an int as if they were a float.

You might get the same result without using unsafe code like this:

public static float sample()
{
    int result = 154 + (153 << 8) + (25 << 16) + (64 << 24);
    return BitConverter.ToSingle(BitConverter.GetBytes(result), 0);
}

But then it won't be very fast any more and you might as well use floats/doubles and the Math functions.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276