Заглядываем в double
Иногда, отлаживая код компилятора или отвечая на вопрос пользователя, у меня появляется необходимость быстро разобрать битовое представление типа double – числа с плавающей запятой. Это не так-то просто, поэтому я собрал некоторый код, который принимает double и выдает о нем все потаенные факты. Я представляю этот код здесь, надеюсь, что он кому-нибудь пригодится. (Замечу, что этот код строился в расчете на простоту использования, а не на производительность; его производительности достаточно для моих целей, так что, я не тратил время на его оптимизацию.)
Чтобы понять формат чисел с плавающей запятой, и почему этот формат именно такой, посмотрите мои предыдущие статьи по этой теме.
Этот код использует класс Rational из MicrosoftSolverFoundation; если у вас еще нет этой библиотеки, то вы можете скачать ее отсюда. Там есть много полезных инструментов! В данном случае мне понадобился тип, который может представлять рациональные числа произвольной точности. Создать собственную простую реализацию класса по работе с рациональными числами весьма просто, особенно если для представления числителя и знаменателя в вашем распоряжении есть класс BigInteger. Но зачем изобретать колесо заново?
Поведение приведенного ниже кода очень простое. Я создал структуру, которая превращает 64-разрядное число с плавающей точкой в 64-разрядное беззнаковое целое, поскольку работать с битами проще с целыми числами. Затем, чтобы не засорять основной код, я создал кучу мелких методов расширения, облегчающие битовые операции с рациональными числами. Я не люблю видеть манипуляции с битами в основном коде. Ну, вы понимаете, о чем я?
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;
for (int bit = 51; bit >= 0; --bit)
fraction += d.Mantissa.Bit(bit) * two.Exp(bit - 52);
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 = 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);
}
}