Nuclex.Support/Documents/DoubleConverter.txt

219 lines
7.3 KiB
Plaintext
Raw Permalink Normal View History

From http://www.yoda.arachsys.com/csharp/DoubleConverter.cs
using System;
using System.Globalization;
/// <summary>
/// A class to allow the conversion of doubles to string representations of
/// their exact decimal values. The implementation aims for readability over
/// efficiency.
/// </summary>
public class DoubleConverter
{
/// <summary>
/// Converts the given double to a string representation of its
/// exact decimal value.
/// </summary>
/// <param name="d">The double to convert.</param>
/// <returns>A string representation of the double's exact decimal value.</return>
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 "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
if (negative)
return "-"+ad.ToString();
else
return ad.ToString();
}
/// <summary>Private class used for manipulating
class ArbitraryDecimal
{
/// <summary>Digits in the decimal expansion, one byte per digit
byte[] digits;
/// <summary>
/// How many digits are *after* the decimal point
/// </summary>
int decimalPoint=0;
/// <summary>
/// Constructs an arbitrary decimal expansion from the given long.
/// The long must not be negative.
/// </summary>
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();
}
/// <summary>
/// Multiplies the current expansion by the given amount, which should
/// only be 2 or 5.
/// </summary>
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();
}
/// <summary>
/// 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.
/// </summary>
internal void Shift (int amount)
{
decimalPoint += amount;
}
/// <summary>
/// Removes leading/trailing zeroes from the expansion.
/// </summary>
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;
}
/// <summary>
/// Converts the value to a proper decimal string representation.
/// </summary>
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
return new string (digitString, 0,
digitString.Length-decimalPoint)+
"."+
new string (digitString,
digitString.Length-decimalPoint,
decimalPoint);
}
}
}