| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- using System;
- using System.Globalization;
- namespace MiscUtil.Conversion
- {
- /// <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.</returns>
- 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);
- 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 sequences of decimal digits.
- /// </summary>
- class ArbitraryDecimal
- {
- /// <summary>Digits in the decimal expansion, one byte per digit</summary>
- 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);
- }
- }
- }
- }
|