Today’s Little Program takes a 64-bit integer and reinterprets its physical representation as a double-precision floating point number.
using System; class Program { static double ReinterpretAsDouble(long longValue) { return BitConverter.ToDouble(BitConverter.GetBytes(longValue), 0); } static long ReinterpretAsLong(double doubleValue) { return BitConverter.ToInt64(BitConverter.GetBytes(doubleValue), 0); } static void Main() { Console.WriteLine(ReinterpretAsDouble(0x4000000000000000)); Console.WriteLine("{0:X}", ReinterpretAsLong(2.0)); } }
Our first attempt uses the BitConverter
class to convert the 64-bit integer to an array of bytes, and then parses a double-precision floating point number from that byte array.
Maybe you’re not happy that this creates a short-lived byte[]
array that will need to be GC’d. So here’s another version that is a little sneakier.
using System; using System.Runtime.InteropServices; class Program { [StructLayout(LayoutKind.Explicit)] struct LongAndDouble { [FieldOffset(0)] public long longValue; [FieldOffset(0)] public double doubleValue; } static double ReinterpretAsDouble(long longValue) { LongAndDouble both; both.doubleValue = 0.0; both.longValue = longValue; return both.doubleValue; } static long ReinterpretAsLong(double doubleValue) { LongAndDouble both; both.longValue = 0; both.doubleValue = doubleValue; return both.longValue; } ... }
This version creates a structure with an unusual layout: The two members occupy the same physical storage. The conversion is done by storing the 64-bit integer into that storage location, then reading the double-precision floating point value out.
There’s a third method that involves writing the 64-bit integer to a memory stream via BinaryWriter
then reading it back with BinaryReader
, but this is clearly inferior to the BitConverter
so I didn’t bother writing it up.
Update: Damien points out that this functionality already exists in the BCL: BitConverter.DoubleToInt64Bits
and BitConverter.Int64BitsToDouble
. But there doesn’t appear to be a BitConverter.FloatToInt32Bits
method, so the techniques discussed above are not completely useless.
Exercise: Why did I have to initialize the doubleValue
before writing to longValue
, and vice versa? What are the implications of the answer to the above question? (Yes, I could have written LongAndDouble both = new LongAndDouble();
, which automatically zero-initializes everything, but then I wouldn’t have had an interesting exercise!)
0 comments