DoubleConverter.cs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. using System;
  2. using System.Globalization;
  3. namespace MiscUtil.Conversion
  4. {
  5. /// <summary>
  6. /// A class to allow the conversion of doubles to string representations of
  7. /// their exact decimal values. The implementation aims for readability over
  8. /// efficiency.
  9. /// </summary>
  10. public class DoubleConverter
  11. {
  12. /// <summary>
  13. /// Converts the given double to a string representation of its
  14. /// exact decimal value.
  15. /// </summary>
  16. /// <param name="d">The double to convert.</param>
  17. /// <returns>A string representation of the double's exact decimal value.</returns>
  18. public static string ToExactString (double d)
  19. {
  20. if (double.IsPositiveInfinity(d))
  21. return "+Infinity";
  22. if (double.IsNegativeInfinity(d))
  23. return "-Infinity";
  24. if (double.IsNaN(d))
  25. return "NaN";
  26. // Translate the double into sign, exponent and mantissa.
  27. long bits = BitConverter.DoubleToInt64Bits(d);
  28. bool negative = (bits < 0);
  29. int exponent = (int) ((bits >> 52) & 0x7ffL);
  30. long mantissa = bits & 0xfffffffffffffL;
  31. // Subnormal numbers; exponent is effectively one higher,
  32. // but there's no extra normalisation bit in the mantissa
  33. if (exponent==0)
  34. {
  35. exponent++;
  36. }
  37. // Normal numbers; leave exponent as it is but add extra
  38. // bit to the front of the mantissa
  39. else
  40. {
  41. mantissa = mantissa | (1L<<52);
  42. }
  43. // Bias the exponent. It's actually biased by 1023, but we're
  44. // treating the mantissa as m.0 rather than 0.m, so we need
  45. // to subtract another 52 from it.
  46. exponent -= 1075;
  47. if (mantissa == 0)
  48. {
  49. return "0";
  50. }
  51. /* Normalize */
  52. while((mantissa & 1) == 0)
  53. { /* i.e., Mantissa is even */
  54. mantissa >>= 1;
  55. exponent++;
  56. }
  57. // Construct a new decimal expansion with the mantissa
  58. ArbitraryDecimal ad = new ArbitraryDecimal (mantissa);
  59. // If the exponent is less than 0, we need to repeatedly
  60. // divide by 2 - which is the equivalent of multiplying
  61. // by 5 and dividing by 10.
  62. if (exponent < 0)
  63. {
  64. for (int i=0; i < -exponent; i++)
  65. ad.MultiplyBy(5);
  66. ad.Shift(-exponent);
  67. }
  68. // Otherwise, we need to repeatedly multiply by 2
  69. else
  70. {
  71. for (int i=0; i < exponent; i++)
  72. ad.MultiplyBy(2);
  73. }
  74. // Finally, return the string with an appropriate sign
  75. if (negative)
  76. return "-"+ad.ToString();
  77. else
  78. return ad.ToString();
  79. }
  80. /// <summary>
  81. /// Private class used for manipulating sequences of decimal digits.
  82. /// </summary>
  83. class ArbitraryDecimal
  84. {
  85. /// <summary>Digits in the decimal expansion, one byte per digit</summary>
  86. byte[] digits;
  87. /// <summary>
  88. /// How many digits are *after* the decimal point
  89. /// </summary>
  90. int decimalPoint=0;
  91. /// <summary>
  92. /// Constructs an arbitrary decimal expansion from the given long.
  93. /// The long must not be negative.
  94. /// </summary>
  95. internal ArbitraryDecimal (long x)
  96. {
  97. string tmp = x.ToString(CultureInfo.InvariantCulture);
  98. digits = new byte[tmp.Length];
  99. for (int i=0; i < tmp.Length; i++)
  100. digits[i] = (byte) (tmp[i]-'0');
  101. Normalize();
  102. }
  103. /// <summary>
  104. /// Multiplies the current expansion by the given amount, which should
  105. /// only be 2 or 5.
  106. /// </summary>
  107. internal void MultiplyBy(int amount)
  108. {
  109. byte[] result = new byte[digits.Length+1];
  110. for (int i=digits.Length-1; i >= 0; i--)
  111. {
  112. int resultDigit = digits[i]*amount+result[i+1];
  113. result[i]=(byte)(resultDigit/10);
  114. result[i+1]=(byte)(resultDigit%10);
  115. }
  116. if (result[0] != 0)
  117. {
  118. digits=result;
  119. }
  120. else
  121. {
  122. Array.Copy (result, 1, digits, 0, digits.Length);
  123. }
  124. Normalize();
  125. }
  126. /// <summary>
  127. /// Shifts the decimal point; a negative value makes
  128. /// the decimal expansion bigger (as fewer digits come after the
  129. /// decimal place) and a positive value makes the decimal
  130. /// expansion smaller.
  131. /// </summary>
  132. internal void Shift (int amount)
  133. {
  134. decimalPoint += amount;
  135. }
  136. /// <summary>
  137. /// Removes leading/trailing zeroes from the expansion.
  138. /// </summary>
  139. internal void Normalize()
  140. {
  141. int first;
  142. for (first=0; first < digits.Length; first++)
  143. if (digits[first]!=0)
  144. break;
  145. int last;
  146. for (last=digits.Length-1; last >= 0; last--)
  147. if (digits[last]!=0)
  148. break;
  149. if (first==0 && last==digits.Length-1)
  150. return;
  151. byte[] tmp = new byte[last-first+1];
  152. for (int i=0; i < tmp.Length; i++)
  153. tmp[i]=digits[i+first];
  154. decimalPoint -= digits.Length-(last+1);
  155. digits=tmp;
  156. }
  157. /// <summary>
  158. /// Converts the value to a proper decimal string representation.
  159. /// </summary>
  160. public override String ToString()
  161. {
  162. char[] digitString = new char[digits.Length];
  163. for (int i=0; i < digits.Length; i++)
  164. digitString[i] = (char)(digits[i]+'0');
  165. // Simplest case - nothing after the decimal point,
  166. // and last real digit is non-zero, eg value=35
  167. if (decimalPoint==0)
  168. {
  169. return new string (digitString);
  170. }
  171. // Fairly simple case - nothing after the decimal
  172. // point, but some 0s to add, eg value=350
  173. if (decimalPoint < 0)
  174. {
  175. return new string (digitString)+
  176. new string ('0', -decimalPoint);
  177. }
  178. // Nothing before the decimal point, eg 0.035
  179. if (decimalPoint >= digitString.Length)
  180. {
  181. return "0."+
  182. new string ('0',(decimalPoint-digitString.Length))+
  183. new string (digitString);
  184. }
  185. // Most complicated case - part of the string comes
  186. // before the decimal point, part comes after it,
  187. // eg 3.5
  188. return new string (digitString, 0,
  189. digitString.Length-decimalPoint)+
  190. "."+
  191. new string (digitString,
  192. digitString.Length-decimalPoint,
  193. decimalPoint);
  194. }
  195. }
  196. }
  197. }