Number.Dragon4.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. namespace System
  5. {
  6. internal static partial class Number
  7. {
  8. private static unsafe void Dragon4(double value, int precision, ref NumberBuffer number)
  9. {
  10. const double Log10V2 = 0.30102999566398119521373889472449;
  11. // DriftFactor = 1 - Log10V2 - epsilon (a small number account for drift of floating point multiplication)
  12. const double DriftFactor = 0.69;
  13. // ========================================================================================================================================
  14. // This implementation is based on the paper: https://www.cs.indiana.edu/~dyb/pubs/FP-Printing-PLDI96.pdf
  15. // Besides the paper, some of the code and ideas are modified from http://www.ryanjuckett.com/programming/printing-floating-point-numbers/
  16. // You must read these two materials to fully understand the code.
  17. //
  18. // Note: we only support fixed format input.
  19. // ========================================================================================================================================
  20. //
  21. // Overview:
  22. //
  23. // The input double number can be represented as:
  24. // value = f * 2^e = r / s.
  25. //
  26. // f: the output mantissa. Note: f is not the 52 bits mantissa of the input double number.
  27. // e: biased exponent.
  28. // r: numerator.
  29. // s: denominator.
  30. // k: value = d0.d1d2 . . . dn * 10^k
  31. // Step 1:
  32. // Extract meta data from the input double value.
  33. //
  34. // Refer to IEEE double precision floating point format.
  35. ulong f = (ulong)(ExtractFractionAndBiasedExponent(value, out int e));
  36. int mantissaHighBitIndex = (e == -1074) ? (int)(BigInteger.LogBase2(f)) : 52;
  37. // Step 2:
  38. // Estimate k. We'll verify it and fix any error later.
  39. //
  40. // This is an improvement of the estimation in the original paper.
  41. // Inspired by http://www.ryanjuckett.com/programming/printing-floating-point-numbers/
  42. //
  43. // LOG10V2 = 0.30102999566398119521373889472449
  44. // DRIFT_FACTOR = 0.69 = 1 - log10V2 - epsilon (a small number account for drift of floating point multiplication)
  45. int k = (int)(Math.Ceiling(((mantissaHighBitIndex + e) * Log10V2) - DriftFactor));
  46. // Step 3:
  47. // Store the input double value in BigInteger format.
  48. //
  49. // To keep the precision, we represent the double value as r/s.
  50. // We have several optimization based on following table in the paper.
  51. //
  52. // ----------------------------------------------------------------------------------------------------------
  53. // | e >= 0 | e < 0 |
  54. // ----------------------------------------------------------------------------------------------------------
  55. // | f != b^(P - 1) | f = b^(P - 1) | e = min exp or f != b^(P - 1) | e > min exp and f = b^(P - 1) |
  56. // --------------------------------------------------------------------------------------------------------------
  57. // | r | f * b^e * 2 | f * b^(e + 1) * 2 | f * 2 | f * b * 2 |
  58. // --------------------------------------------------------------------------------------------------------------
  59. // | s | 2 | b * 2 | b^(-e) * 2 | b^(-e + 1) * 2 |
  60. // --------------------------------------------------------------------------------------------------------------
  61. //
  62. // Note, we do not need m+ and m- because we only support fixed format input here.
  63. // m+ and m- are used for free format input, which need to determine the exact range of values
  64. // that would round to value when input so that we can generate the shortest correct number.digits.
  65. //
  66. // In our case, we just output number.digits until reaching the expected precision.
  67. var r = new BigInteger(f);
  68. var s = new BigInteger(0);
  69. if (e >= 0)
  70. {
  71. // When f != b^(P - 1):
  72. // r = f * b^e * 2
  73. // s = 2
  74. // value = r / s = f * b^e * 2 / 2 = f * b^e / 1
  75. //
  76. // When f = b^(P - 1):
  77. // r = f * b^(e + 1) * 2
  78. // s = b * 2
  79. // value = r / s = f * b^(e + 1) * 2 / b * 2 = f * b^e / 1
  80. //
  81. // Therefore, we can simply say that when e >= 0:
  82. // r = f * b^e = f * 2^e
  83. // s = 1
  84. r.ShiftLeft((uint)(e));
  85. s.SetUInt64(1);
  86. }
  87. else
  88. {
  89. // When e = min exp or f != b^(P - 1):
  90. // r = f * 2
  91. // s = b^(-e) * 2
  92. // value = r / s = f * 2 / b^(-e) * 2 = f / b^(-e)
  93. //
  94. // When e > min exp and f = b^(P - 1):
  95. // r = f * b * 2
  96. // s = b^(-e + 1) * 2
  97. // value = r / s = f * b * 2 / b^(-e + 1) * 2 = f / b^(-e)
  98. //
  99. // Therefore, we can simply say that when e < 0:
  100. // r = f
  101. // s = b^(-e) = 2^(-e)
  102. BigInteger.ShiftLeft(1, (uint)(-e), ref s);
  103. }
  104. // According to the paper, we should use k >= 0 instead of k > 0 here.
  105. // However, if k = 0, both r and s won't be changed, we don't need to do any operation.
  106. //
  107. // Following are the Scheme code from the paper:
  108. // --------------------------------------------------------------------------------
  109. // (if (>= est 0)
  110. // (fixup r (* s (exptt B est)) m+ m− est B low-ok? high-ok? )
  111. // (let ([scale (exptt B (− est))])
  112. // (fixup (* r scale) s (* m+ scale) (* m− scale) est B low-ok? high-ok? ))))
  113. // --------------------------------------------------------------------------------
  114. //
  115. // If est is 0, (* s (exptt B est)) = s, (* r scale) = (* r (exptt B (− est)))) = r.
  116. //
  117. // So we just skip when k = 0.
  118. if (k > 0)
  119. {
  120. s.MultiplyPow10((uint)(k));
  121. }
  122. else if (k < 0)
  123. {
  124. r.MultiplyPow10((uint)(-k));
  125. }
  126. if (BigInteger.Compare(ref r, ref s) >= 0)
  127. {
  128. // The estimation was incorrect. Fix the error by increasing 1.
  129. k += 1;
  130. }
  131. else
  132. {
  133. r.Multiply10();
  134. }
  135. number.Scale = (k - 1);
  136. // This the prerequisite of calling BigInteger.HeuristicDivide().
  137. BigInteger.PrepareHeuristicDivide(ref r, ref s);
  138. // Step 4:
  139. // Calculate number.digits.
  140. //
  141. // Output number.digits until reaching the last but one precision or the numerator becomes zero.
  142. int digitsNum = 0;
  143. int currentDigit = 0;
  144. while (true)
  145. {
  146. currentDigit = (int)(BigInteger.HeuristicDivide(ref r, ref s));
  147. if (r.IsZero() || ((digitsNum + 1) == precision))
  148. {
  149. break;
  150. }
  151. number.Digits[digitsNum] = (byte)('0' + currentDigit);
  152. digitsNum++;
  153. r.Multiply10();
  154. }
  155. // Step 5:
  156. // Set the last digit.
  157. //
  158. // We round to the closest digit by comparing value with 0.5:
  159. // compare( value, 0.5 )
  160. // = compare( r / s, 0.5 )
  161. // = compare( r, 0.5 * s)
  162. // = compare(2 * r, s)
  163. // = compare(r << 1, s)
  164. r.ShiftLeft(1);
  165. int compareResult = BigInteger.Compare(ref r, ref s);
  166. bool isRoundDown = compareResult < 0;
  167. // We are in the middle, round towards the even digit (i.e. IEEE rouding rules)
  168. if (compareResult == 0)
  169. {
  170. isRoundDown = (currentDigit & 1) == 0;
  171. }
  172. if (isRoundDown)
  173. {
  174. number.Digits[digitsNum] = (byte)('0' + currentDigit);
  175. digitsNum++;
  176. }
  177. else
  178. {
  179. byte* pCurrentDigit = (number.GetDigitsPointer() + digitsNum);
  180. // Rounding up for 9 is special.
  181. if (currentDigit == 9)
  182. {
  183. // find the first non-nine prior digit
  184. while (true)
  185. {
  186. // If we are at the first digit
  187. if (pCurrentDigit == number.GetDigitsPointer())
  188. {
  189. // Output 1 at the next highest exponent
  190. *pCurrentDigit = (byte)('1');
  191. digitsNum++;
  192. number.Scale += 1;
  193. break;
  194. }
  195. pCurrentDigit--;
  196. digitsNum--;
  197. if (*pCurrentDigit != '9')
  198. {
  199. // increment the digit
  200. *pCurrentDigit += 1;
  201. digitsNum++;
  202. break;
  203. }
  204. }
  205. }
  206. else
  207. {
  208. // It's simple if the digit is not 9.
  209. *pCurrentDigit = (byte)('0' + currentDigit + 1);
  210. digitsNum++;
  211. }
  212. }
  213. while (digitsNum < precision)
  214. {
  215. number.Digits[digitsNum] = (byte)('0');
  216. digitsNum++;
  217. }
  218. number.Digits[precision] = (byte)('\0');
  219. number.Scale++;
  220. number.IsNegative = double.IsNegative(value);
  221. }
  222. }
  223. }