Looking inside a double
Occasionally when I'm debugging the compiler or responding to a user question I'll need to quickly take apart the bits of a double-precision floating point number. Doing so is a bit of a pain, so I've whipped up some quick code that takes a double and tells you all the salient facts about it. I present it here, should you have any use for it yourself. (Note that this code was built for comfort, not speed; it is more than fast enough for my purposes so I've spent zero time optimizing it.)
To understand the format of a double and why it is the way it is, see my earlier articles on the subject.
This code uses the Rational class from the Microsoft Solver Foundation; you can download the code from here if you haven't got it already. There are some handy tools in there! In this particular case I needed a type that could represent a rational number of arbitrarily high precision. Building your own naive implementation of rationals is very straightforward, particularly if you already have a BigInteger type to be the numerator and denominator. But why re-invent the wheel?
The action of the code below is extremely straightforward. I make a struct that turns a 64 bit double into a 64 bit unsigned integer, since it is easier to get the bits out of an integer. I then build a bunch of tiny little extension methods that make it easier to work with bits, with rationals, and so on, so that the mainline code does not become an awful mess. I hate seeing bit twiddling in mainline code, you know what I mean?
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SolverFoundation.Common;
class Program
{
static void Main()
{
double original = -256.325;
MyDouble d = original;
Console.WriteLine("Raw sign: {0}", d.Sign);
Console.WriteLine("Raw exponent: {0}", d.ExponentBits.Join());
Console.WriteLine("Raw mantissa: {0}", d.MantissaBits.Join());
var signchar = d.Sign == 0 ? '+' : '-';
if (d.Exponent == 0 && d.Mantissa == 0)
{
Console.WriteLine("Zero: {0}0", signchar);
return;
}
else if (d.Exponent == 0x7ff && d.Mantissa == 0)
{
Console.WriteLine("Infinity: {0}Infinity", signchar);
return;
}
else if (d.Exponent == 0x7ff)
{
Console.WriteLine("NaN");
return;
}
bool subnormal = d.Exponent == 0;
var two = (Rational)2;
var fraction = subnormal ? Rational.Zero : Rational.One;
var adjust = subnormal ? 1 : 0;
for (int bit = 51; bit >= 0; --bit)
fraction += d.Mantissa.Bit(bit) * two.Exp(bit - 52 + adjust);
fraction = fraction * two.Exp(d.Exponent - 1023);
if (d.Sign == 1)
fraction = -fraction;
Console.WriteLine(subnormal ? "Subnormal" : "Normal");
Console.WriteLine("Sign: {0}", signchar);
Console.WriteLine("Exponent: {0}", d.Exponent - 1023);
Console.WriteLine("Exact binary fraction: {0}.{1}", subnormal ? 0 : 1, d.MantissaBits.Join());
Console.WriteLine("Nearest approximate decimal: {0}", original);
Console.WriteLine("Exact rational fraction: {0}", fraction.ToString());
Console.WriteLine("Exact decimal fraction: {0}", fraction.ToDecimalString());
}
}
struct MyDouble
{
private ulong bits;
public MyDouble(double d)
{
this.bits = (ulong)BitConverter.DoubleToInt64Bits(d);
}
public int Sign
{
get
{
return this.bits.Bit(63);
}
}
public int Exponent
{
get
{
return (int)this.bits.Bits(62, 52);
}
}
public IEnumerable<int> ExponentBits
{
get
{
return this.bits.BitSeq(62, 52);
}
}
public ulong Mantissa
{
get
{
return this.bits.Bits(51, 0);
}
}
public IEnumerable<int> MantissaBits
{
get
{
return this.bits.BitSeq(51, 0);
}
}
public static implicit operator MyDouble(double d)
{
return new MyDouble(d);
}
}
static class Extensions
{
public static int Bit(this ulong x, int bit)
{
return (int)((x >> bit) & 0x01);
}
public static ulong Bits(this ulong x, int high, int low)
{
x <<= (63 - high);
x >>= (low + 63 - high);
return x;
}
public static IEnumerable<int> BitSeq(this ulong x, int high, int low)
{
for(int bit = high; bit >= low; --bit)
yield return x.Bit(bit);
}
public static Rational Exp(this Rational x, int y)
{
Rational result;
Rational.Power(x, y, out result);
return result;
}
public static string ToDecimalString(this Rational x)
{
var sb = new StringBuilder();
x.AppendDecimalString(sb, 50000);
return sb.ToString();
}
public static string Join<T>(this IEnumerable<T> seq)
{
return string.Concat(seq);
}
}
Comments
Anonymous
February 16, 2011
The comment has been removedAnonymous
February 17, 2011
The comment has been removedAnonymous
February 17, 2011
Why not use BitConverter.DoubleToInt64Bits? Because I didn't know there was any such method. Your idea has great merit and will be implemented immediately. Thanks! -- EricAnonymous
February 17, 2011
You don't distinguish the two types of NaN (quiet and signalling)... is that because there's no way to get an SNaN into the variable "original"?Anonymous
February 18, 2011
The comment has been removedAnonymous
February 20, 2011
You DO bit-twiddling in the main-code: ... d.Exponent == 0x7ff && d.Mantissa == 0 better convert it to a method of myDouble: bool IsInfinite { get { return this.Exponent == 0x7ff && this.Mantissa == 0;} } The sames goes for NaN, etc.Anonymous
October 05, 2011
The weird thing is that there is a DoubleToInt64Bits method, but no SingleToInt32Bits method!