9

I am writing various types to a byte stream by manually casting and shifting values. I have found this to be more than three times faster than using BitConverter or BinaryWriter.

My problem is with floats. I need to cast them to ints in order to perform shift operations on them, but any cast to int will cause an implicit conversion with truncation, etc. I want the keep the exact binary representation the same. Is this possible?

eg. I want to be able to do similar to:

byte[] bytes = new byte[4];
float myFloat = 32.2;

//following won't compile as can't shift floats.
bytes [0] = (byte)myFloat;
bytes [1] = (byte)(myFloat >> 8);
bytes [2] = (byte)(myFloat >> 16);
bytes [3] = (byte)(myFloat >> 24);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
GazTheDestroyer
  • 20,722
  • 9
  • 70
  • 103

3 Answers3

15

The following does not require unsafe code and is proved to work (I used it for years across various .NET versions):

[StructLayout(LayoutKind.Explicit)]
public struct FloatAndUIntUnion
{
    [FieldOffset(0)]
    public uint  UInt32Bits;
    [FieldOffset(0)]
    public float FloatValue;
}


FloatAndUIntUnion f2i = default(FloatAndUIntUnion);
f2i.FloatValue = aFloat;    // write as float
uint i = f2i.UInt32Bits;    // read back as int

Note that the float→int direction can be simpler:

int i = aFloat.GetHashCode();

However, it is much more obscure because not documented (as per MSDN), although confirmed by this post. (That is, the behaviour was the same in 2006 as now in 2013, and I see no reason to change it in the future — but it may happen, and I'm unsure whether it would qualify as a backward-incompatible change or not.)

robert4
  • 1,072
  • 15
  • 20
  • 2
    Technically you shouldn't rely on the behaviour - but as homay2 says, I doubt it will change. Maybe protect your code with a unit test carrying some explanatory comments? – James World May 29 '13 at 19:40
  • 1
    This approach is also used by Microsoft in Microsoft.CodeAnalysis, as seen on Roslyn Reference Source: http://source.roslyn.codeplex.com/#Microsoft.CodeAnalysis/RealParser.cs,768 – Drew Noakes Dec 08 '15 at 21:24
  • Also, there's a PR out for `BitConverter.SingleToInt32Bits`: https://github.com/dotnet/corefx/issues/4673 – Drew Noakes Dec 08 '15 at 21:29
6

Can you use double instead of float?

double doubleValue = 32.2;
long setofBits = BitConverter.DoubleToInt64Bits(doubleValue);

EDIT: Answer to comment

I believe there is no overhead using BitConverter, using Reflector we can see that it's implementation is pretty simple:

public static unsafe long DoubleToInt64Bits(double value)
{
    return *(((long*) &value));
}
sll
  • 61,540
  • 22
  • 104
  • 156
  • Unfortunately the whole point of me doing this is to avoid the overhead of BitConverter. – GazTheDestroyer Nov 07 '11 at 14:21
  • Interesting! However my results clearly show a > 3x speed up by hand shifting, but this is in a very tight loop. I suspect the temporary byte[] arrays being returned from Bitconverter are causing the issue. – GazTheDestroyer Nov 07 '11 at 14:35
  • How you able to measure such a fast operation? have you tried to loop ~1M times the convert/shift operation? – sll Nov 07 '11 at 14:42
  • I'm looping 250,000,000 times. I'm aware that such tests are not exact by any means but the test is very similar to the production code so such an obvious speed difference bares investigation. – GazTheDestroyer Nov 07 '11 at 14:54
  • How you measure time? Are you using `Stopwatch`? – sll Nov 07 '11 at 14:58
  • Yup, using StopWatch. As I said, I suspect the temporary arrays are causing GC collections. – GazTheDestroyer Nov 07 '11 at 15:01
5
float myFloat = 0.124112f;
float[] floats = new[] { myFloat };
byte[] bytes = new byte[4];
Buffer.BlockCopy(floats, 0, bytes, 0, 4);

?

EDIT: compared to DoubleToInt64Bits its slower though like 4x times on my machine, which makes sense given its array nature and inevitable overhead when compared to extreme simplicity of DoubleToInt64Bits implementation.

Valentin Kuzub
  • 11,703
  • 7
  • 56
  • 93