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.[1. 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 naïve 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);     
  } 
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s