using System;
using System.Globalization;
///
/// A class to allow the conversion of doubles to string representations of
/// their exact decimal values. The implementation aims for readability over
/// efficiency.
///
public class DoubleConverter
{
///
/// Converts the given double to a string representation of its
/// exact decimal value.
///
/// The double to convert.
/// A string representation of the double's exact decimal value.
public static string ToExactString(double d)
{
if (double.IsPositiveInfinity(d))
{
return "+Infinity";
}
if (double.IsNegativeInfinity(d))
{
return "-Infinity";
}
if (double.IsNaN(d))
{
return "NaN";
}
// Translate the double into sign, exponent and mantissa.
long bits = BitConverter.DoubleToInt64Bits(d);
// Note that the shift is sign-extended, hence the test against -1 not 1
bool negative = (bits < 0);
int exponent = (int) ((bits >> 52) & 0x7ffL);
long mantissa = bits & 0xfffffffffffffL;
// Subnormal numbers; exponent is effectively one higher,
// but there's no extra normalisation bit in the mantissa
if (exponent == 0)
{
exponent++;
}
// Normal numbers; leave exponent as it is but add extra
// bit to the front of the mantissa
else
{
mantissa = mantissa | (1L << 52);
}
// Bias the exponent. It's actually biased by 1023, but we're
// treating the mantissa as m.0 rather than 0.m, so we need
// to subtract another 52 from it.
exponent -= 1075;
if (mantissa == 0)
{
return negative ? "-0" : "0";
}
/* Normalize */
while ((mantissa & 1) == 0)
{ /* i.e., Mantissa is even */
mantissa >>= 1;
exponent++;
}
/// Construct a new decimal expansion with the mantissa
ArbitraryDecimal ad = new ArbitraryDecimal(mantissa);
// If the exponent is less than 0, we need to repeatedly
// divide by 2 - which is the equivalent of multiplying
// by 5 and dividing by 10.
if (exponent < 0)
{
for (int i = 0; i < -exponent; i++)
{
ad.MultiplyBy(5);
}
ad.Shift(-exponent);
}
// Otherwise, we need to repeatedly multiply by 2
else
{
for (int i = 0; i < exponent; i++)
{
ad.MultiplyBy(2);
}
}
// Finally, return the string with an appropriate sign
return negative ? "-" + ad : ad.ToString();
}
/// Private class used for manipulating
class ArbitraryDecimal
{
/// Digits in the decimal expansion, one byte per digit
byte[] digits;
///
/// How many digits are *after* the decimal point
///
int decimalPoint = 0;
///
/// Constructs an arbitrary decimal expansion from the given long.
/// The long must not be negative.
///
internal ArbitraryDecimal(long x)
{
string tmp = x.ToString(CultureInfo.InvariantCulture);
digits = new byte[tmp.Length];
for (int i = 0; i < tmp.Length; i++)
{
digits[i] = (byte) (tmp[i] - '0');
}
Normalize();
}
///
/// Multiplies the current expansion by the given amount, which should
/// only be 2 or 5.
///
internal void MultiplyBy(int amount)
{
byte[] result = new byte[digits.Length + 1];
for (int i = digits.Length - 1; i >= 0; i--)
{
int resultDigit = digits[i] * amount + result[i + 1];
result[i] = (byte) (resultDigit / 10);
result[i + 1] = (byte) (resultDigit % 10);
}
if (result[0] != 0)
{
digits = result;
}
else
{
Array.Copy(result, 1, digits, 0, digits.Length);
}
Normalize();
}
///
/// Shifts the decimal point; a negative value makes
/// the decimal expansion bigger (as fewer digits come after the
/// decimal place) and a positive value makes the decimal
/// expansion smaller.
///
internal void Shift(int amount)
{
decimalPoint += amount;
}
///
/// Removes leading/trailing zeroes from the expansion.
///
internal void Normalize()
{
int first;
for (first = 0; first < digits.Length; first++)
{
if (digits[first] != 0)
{
break;
}
}
int last;
for (last = digits.Length - 1; last >= 0; last--)
{
if (digits[last] != 0)
{
break;
}
}
if (first == 0 && last == digits.Length - 1)
{
return;
}
byte[] tmp = new byte[last - first + 1];
for (int i = 0; i < tmp.Length; i++)
{
tmp[i] = digits[i + first];
}
decimalPoint -= digits.Length - (last + 1);
digits = tmp;
}
///
/// Converts the value to a proper decimal string representation.
///
public override String ToString()
{
char[] digitString = new char[digits.Length];
for (int i = 0; i < digits.Length; i++)
{
digitString[i] = (char) (digits[i] + '0');
}
// Simplest case - nothing after the decimal point,
// and last real digit is non-zero, eg value=35
if (decimalPoint == 0)
{
return new string(digitString);
}
// Fairly simple case - nothing after the decimal
// point, but some 0s to add, eg value=350
if (decimalPoint < 0)
{
return new string(digitString) +
new string('0', -decimalPoint);
}
// Nothing before the decimal point, eg 0.035
if (decimalPoint >= digitString.Length)
{
return "0." +
new string('0', (decimalPoint - digitString.Length)) +
new string(digitString);
}
// Most complicated case - part of the string comes
// before the decimal point, part comes after it, eg 3.5.
string integerPart = new string(digitString, 0, digitString.Length - decimalPoint);
string fractionalPart = new string(digitString, digitString.Length - decimalPoint, decimalPoint);
return integerPart + "." + fractionalPart;
}
}
}