Fix IDE0025 (use expression body for properties)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Number.Dragon4.cs
blob24e5ab224e852575b3821985c2fdac14e3b4b2a8
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.
5 using System.Diagnostics;
6 using System.Numerics;
7 using Internal.Runtime.CompilerServices;
9 namespace System
11 // This is a port of the `Dragon4` implementation here: http://www.ryanjuckett.com/programming/printing-floating-point-numbers/part-2/
12 // The backing algorithm and the proofs behind it are described in more detail here: https://www.cs.indiana.edu/~dyb/pubs/FP-Printing-PLDI96.pdf
13 internal static partial class Number
15 public static void Dragon4Double(double value, int cutoffNumber, bool isSignificantDigits, ref NumberBuffer number)
17 double v = double.IsNegative(value) ? -value : value;
19 Debug.Assert(v > 0);
20 Debug.Assert(double.IsFinite(v));
22 ulong mantissa = ExtractFractionAndBiasedExponent(value, out int exponent);
24 uint mantissaHighBitIdx;
25 bool hasUnequalMargins = false;
27 if ((mantissa >> DiyFp.DoubleImplicitBitIndex) != 0)
29 mantissaHighBitIdx = DiyFp.DoubleImplicitBitIndex;
30 hasUnequalMargins = (mantissa == (1UL << DiyFp.DoubleImplicitBitIndex));
32 else
34 Debug.Assert(mantissa != 0);
35 mantissaHighBitIdx = (uint)BitOperations.Log2(mantissa);
38 int length = (int)(Dragon4(mantissa, exponent, mantissaHighBitIdx, hasUnequalMargins, cutoffNumber, isSignificantDigits, number.Digits, out int decimalExponent));
40 number.Scale = decimalExponent + 1;
41 number.Digits[length] = (byte)('\0');
42 number.DigitsCount = length;
45 public static unsafe void Dragon4Single(float value, int cutoffNumber, bool isSignificantDigits, ref NumberBuffer number)
47 float v = float.IsNegative(value) ? -value : value;
49 Debug.Assert(v > 0);
50 Debug.Assert(float.IsFinite(v));
52 uint mantissa = ExtractFractionAndBiasedExponent(value, out int exponent);
54 uint mantissaHighBitIdx;
55 bool hasUnequalMargins = false;
57 if ((mantissa >> DiyFp.SingleImplicitBitIndex) != 0)
59 mantissaHighBitIdx = DiyFp.SingleImplicitBitIndex;
60 hasUnequalMargins = (mantissa == (1U << DiyFp.SingleImplicitBitIndex));
62 else
64 Debug.Assert(mantissa != 0);
65 mantissaHighBitIdx = (uint)BitOperations.Log2(mantissa);
68 int length = (int)(Dragon4(mantissa, exponent, mantissaHighBitIdx, hasUnequalMargins, cutoffNumber, isSignificantDigits, number.Digits, out int decimalExponent));
70 number.Scale = decimalExponent + 1;
71 number.Digits[length] = (byte)('\0');
72 number.DigitsCount = length;
75 // This is an implementation of the Dragon4 algorithm to convert a binary number in floating-point format to a decimal number in string format.
76 // The function returns the number of digits written to the output buffer and the output is not NUL terminated.
78 // The floating point input value is (mantissa * 2^exponent).
80 // See the following papers for more information on the algorithm:
81 // "How to Print Floating-Point Numbers Accurately"
82 // Steele and White
83 // http://kurtstephens.com/files/p372-steele.pdf
84 // "Printing Floating-Point Numbers Quickly and Accurately"
85 // Burger and Dybvig
86 // http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.72.4656&rep=rep1&type=pdf
87 private static unsafe uint Dragon4(ulong mantissa, int exponent, uint mantissaHighBitIdx, bool hasUnequalMargins, int cutoffNumber, bool isSignificantDigits, Span<byte> buffer, out int decimalExponent)
89 int curDigit = 0;
91 Debug.Assert(buffer.Length > 0);
93 // We deviate from the original algorithm and just assert that the mantissa
94 // is not zero. Comparing to zero is fine since the caller should have set
95 // the implicit bit of the mantissa, meaning it would only ever be zero if
96 // the extracted exponent was also zero. And the assertion is fine since we
97 // require that the DoubleToNumber handle zero itself.
98 Debug.Assert(mantissa != 0);
100 // Compute the initial state in integral form such that
101 // value = scaledValue / scale
102 // marginLow = scaledMarginLow / scale
104 BigInteger scale; // positive scale applied to value and margin such that they can be represented as whole numbers
105 BigInteger scaledValue; // scale * mantissa
106 BigInteger scaledMarginLow; // scale * 0.5 * (distance between this floating-point number and its immediate lower value)
108 // For normalized IEEE floating-point values, each time the exponent is incremented the margin also doubles.
109 // That creates a subset of transition numbers where the high margin is twice the size of the low margin.
110 BigInteger* pScaledMarginHigh;
111 BigInteger optionalMarginHigh;
113 if (hasUnequalMargins)
115 if (exponent > 0) // We have no fractional component
117 // 1) Expand the input value by multiplying out the mantissa and exponent.
118 // This represents the input value in its whole number representation.
119 // 2) Apply an additional scale of 2 such that later comparisons against the margin values are simplified.
120 // 3) Set the margin value to the loweset mantissa bit's scale.
122 // scaledValue = 2 * 2 * mantissa * 2^exponent
123 scaledValue = new BigInteger(4 * mantissa);
124 scaledValue.ShiftLeft((uint)(exponent));
126 // scale = 2 * 2 * 1
127 scale = new BigInteger(4);
129 // scaledMarginLow = 2 * 2^(exponent - 1)
130 BigInteger.Pow2((uint)(exponent), out scaledMarginLow);
132 // scaledMarginHigh = 2 * 2 * 2^(exponent + 1)
133 BigInteger.Pow2((uint)(exponent + 1), out optionalMarginHigh);
135 else // We have a fractional exponent
137 // In order to track the mantissa data as an integer, we store it as is with a large scale
139 // scaledValue = 2 * 2 * mantissa
140 scaledValue = new BigInteger(4 * mantissa);
142 // scale = 2 * 2 * 2^(-exponent)
143 BigInteger.Pow2((uint)(-exponent + 2), out scale);
145 // scaledMarginLow = 2 * 2^(-1)
146 scaledMarginLow = new BigInteger(1);
148 // scaledMarginHigh = 2 * 2 * 2^(-1)
149 optionalMarginHigh = new BigInteger(2);
152 // The high and low margins are different
153 pScaledMarginHigh = &optionalMarginHigh;
155 else
157 if (exponent > 0) // We have no fractional component
159 // 1) Expand the input value by multiplying out the mantissa and exponent.
160 // This represents the input value in its whole number representation.
161 // 2) Apply an additional scale of 2 such that later comparisons against the margin values are simplified.
162 // 3) Set the margin value to the lowest mantissa bit's scale.
164 // scaledValue = 2 * mantissa*2^exponent
165 scaledValue = new BigInteger(2 * mantissa);
166 scaledValue.ShiftLeft((uint)(exponent));
168 // scale = 2 * 1
169 scale = new BigInteger(2);
171 // scaledMarginLow = 2 * 2^(exponent-1)
172 BigInteger.Pow2((uint)(exponent), out scaledMarginLow);
174 else // We have a fractional exponent
176 // In order to track the mantissa data as an integer, we store it as is with a large scale
178 // scaledValue = 2 * mantissa
179 scaledValue = new BigInteger(2 * mantissa);
181 // scale = 2 * 2^(-exponent)
182 BigInteger.Pow2((uint)(-exponent + 1), out scale);
184 // scaledMarginLow = 2 * 2^(-1)
185 scaledMarginLow = new BigInteger(1);
188 // The high and low margins are equal
189 pScaledMarginHigh = &scaledMarginLow;
192 // Compute an estimate for digitExponent that will be correct or undershoot by one.
194 // This optimization is based on the paper "Printing Floating-Point Numbers Quickly and Accurately" by Burger and Dybvig http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.72.4656&rep=rep1&type=pdf
196 // We perform an additional subtraction of 0.69 to increase the frequency of a failed estimate because that lets us take a faster branch in the code.
197 // 0.69 is chosen because 0.69 + log10(2) is less than one by a reasonable epsilon that will account for any floating point error.
199 // We want to set digitExponent to floor(log10(v)) + 1
200 // v = mantissa * 2^exponent
201 // log2(v) = log2(mantissa) + exponent;
202 // log10(v) = log2(v) * log10(2)
203 // floor(log2(v)) = mantissaHighBitIdx + exponent;
204 // log10(v) - log10(2) < (mantissaHighBitIdx + exponent) * log10(2) <= log10(v)
205 // log10(v) < (mantissaHighBitIdx + exponent) * log10(2) + log10(2) <= log10(v) + log10(2)
206 // floor(log10(v)) < ceil((mantissaHighBitIdx + exponent) * log10(2)) <= floor(log10(v)) + 1
207 const double Log10V2 = 0.30102999566398119521373889472449;
208 int digitExponent = (int)(Math.Ceiling(((int)(mantissaHighBitIdx) + exponent) * Log10V2 - 0.69));
210 // Divide value by 10^digitExponent.
211 if (digitExponent > 0)
213 // The exponent is positive creating a division so we multiply up the scale.
214 scale.MultiplyPow10((uint)(digitExponent));
216 else if (digitExponent < 0)
218 // The exponent is negative creating a multiplication so we multiply up the scaledValue, scaledMarginLow and scaledMarginHigh.
220 BigInteger.Pow10((uint)(-digitExponent), out BigInteger pow10);
222 scaledValue.Multiply(ref pow10);
223 scaledMarginLow.Multiply(ref pow10);
225 if (pScaledMarginHigh != &scaledMarginLow)
227 BigInteger.Multiply(ref scaledMarginLow, 2, ref *pScaledMarginHigh);
231 // If (value >= 1), our estimate for digitExponent was too low
232 if (BigInteger.Compare(ref scaledValue, ref scale) >= 0)
234 // The exponent estimate was incorrect.
235 // Increment the exponent and don't perform the premultiply needed for the first loop iteration.
236 digitExponent++;
238 else
240 // The exponent estimate was correct.
241 // Multiply larger by the output base to prepare for the first loop iteration.
242 scaledValue.Multiply10();
243 scaledMarginLow.Multiply10();
245 if (pScaledMarginHigh != &scaledMarginLow)
247 BigInteger.Multiply(ref scaledMarginLow, 2, ref *pScaledMarginHigh);
251 // Compute the cutoff exponent (the exponent of the final digit to print).
252 // Default to the maximum size of the output buffer.
253 int cutoffExponent = digitExponent - buffer.Length;
255 if (cutoffNumber != -1)
257 int desiredCutoffExponent = 0;
259 if (isSignificantDigits)
261 // We asked for a specific number of significant digits.
262 Debug.Assert(cutoffNumber > 0);
263 desiredCutoffExponent = digitExponent - cutoffNumber;
265 else
267 // We asked for a specific number of fractional digits.
268 Debug.Assert(cutoffNumber >= 0);
269 desiredCutoffExponent = -cutoffNumber;
272 if (desiredCutoffExponent > cutoffExponent)
274 // Only select the new cutoffExponent if it won't overflow the destination buffer.
275 cutoffExponent = desiredCutoffExponent;
279 // Output the exponent of the first digit we will print
280 decimalExponent = --digitExponent;
282 // In preparation for calling BigInteger.HeuristicDivie(), we need to scale up our values such that the highest block of the denominator is greater than or equal to 8.
283 // We also need to guarantee that the numerator can never have a length greater than the denominator after each loop iteration.
284 // This requires the highest block of the denominator to be less than or equal to 429496729 which is the highest number that can be multiplied by 10 without overflowing to a new block.
286 Debug.Assert(scale.GetLength() > 0);
287 uint hiBlock = scale.GetBlock((uint)(scale.GetLength() - 1));
289 if ((hiBlock < 8) || (hiBlock > 429496729))
291 // Perform a bit shift on all values to get the highest block of the denominator into the range [8,429496729].
292 // We are more likely to make accurate quotient estimations in BigInteger.HeuristicDivide() with higher denominator values so we shift the denominator to place the highest bit at index 27 of the highest block.
293 // This is safe because (2^28 - 1) = 268435455 which is less than 429496729.
294 // This means that all values with a highest bit at index 27 are within range.
295 Debug.Assert(hiBlock != 0);
296 uint hiBlockLog2 = (uint)BitOperations.Log2(hiBlock);
297 Debug.Assert((hiBlockLog2 < 3) || (hiBlockLog2 > 27));
298 uint shift = (32 + 27 - hiBlockLog2) % 32;
300 scale.ShiftLeft(shift);
301 scaledValue.ShiftLeft(shift);
302 scaledMarginLow.ShiftLeft(shift);
304 if (pScaledMarginHigh != &scaledMarginLow)
306 BigInteger.Multiply(ref scaledMarginLow, 2, ref *pScaledMarginHigh);
310 // These values are used to inspect why the print loop terminated so we can properly round the final digit.
311 bool low; // did the value get within marginLow distance from zero
312 bool high; // did the value get within marginHigh distance from one
313 uint outputDigit; // current digit being output
315 if (cutoffNumber == -1)
317 Debug.Assert(isSignificantDigits);
318 Debug.Assert(digitExponent >= cutoffExponent);
320 // For the unique cutoff mode, we will try to print until we have reached a level of precision that uniquely distinguishes this value from its neighbors.
321 // If we run out of space in the output buffer, we terminate early.
323 while (true)
325 // divide out the scale to extract the digit
326 outputDigit = BigInteger.HeuristicDivide(ref scaledValue, ref scale);
327 Debug.Assert(outputDigit < 10);
329 // update the high end of the value
330 BigInteger.Add(ref scaledValue, ref *pScaledMarginHigh, out BigInteger scaledValueHigh);
332 // stop looping if we are far enough away from our neighboring values or if we have reached the cutoff digit
333 low = BigInteger.Compare(ref scaledValue, ref scaledMarginLow) < 0;
334 high = BigInteger.Compare(ref scaledValueHigh, ref scale) > 0;
336 if (low || high || (digitExponent == cutoffExponent))
338 break;
341 // store the output digit
342 buffer[curDigit] = (byte)('0' + outputDigit);
343 curDigit += 1;
345 // multiply larger by the output base
346 scaledValue.Multiply10();
347 scaledMarginLow.Multiply10();
349 if (pScaledMarginHigh != &scaledMarginLow)
351 BigInteger.Multiply(ref scaledMarginLow, 2, ref *pScaledMarginHigh);
354 digitExponent--;
357 else if (digitExponent >= cutoffExponent)
359 Debug.Assert((cutoffNumber > 0) || ((cutoffNumber == 0) && !isSignificantDigits));
361 // For length based cutoff modes, we will try to print until we have exhausted all precision (i.e. all remaining digits are zeros) or until we reach the desired cutoff digit.
362 low = false;
363 high = false;
365 while (true)
367 // divide out the scale to extract the digit
368 outputDigit = BigInteger.HeuristicDivide(ref scaledValue, ref scale);
369 Debug.Assert(outputDigit < 10);
371 if (scaledValue.IsZero() || (digitExponent <= cutoffExponent))
373 break;
376 // store the output digit
377 buffer[curDigit] = (byte)('0' + outputDigit);
378 curDigit += 1;
380 // multiply larger by the output base
381 scaledValue.Multiply10();
382 digitExponent--;
385 else
387 // In the scenario where the first significant digit is after the cutoff, we want to treat that
388 // first significant digit as the rounding digit. If the first significant would cause the next
389 // digit to round, we will increase the decimalExponent by one and set the previous digit to one.
390 // This ensures we correctly handle the case where the first significant digit is exactly one after
391 // the cutoff, it is a 4, and the subsequent digit would round that to 5 inducing a double rounding
392 // bug when NumberToString does its own rounding checks. However, if the first significant digit
393 // would not cause the next one to round, we preserve that digit as is.
395 // divide out the scale to extract the digit
396 outputDigit = BigInteger.HeuristicDivide(ref scaledValue, ref scale);
397 Debug.Assert((0 < outputDigit) && (outputDigit < 10));
399 if ((outputDigit > 5) || ((outputDigit == 5) && !scaledValue.IsZero()))
401 decimalExponent++;
402 outputDigit = 1;
405 buffer[curDigit] = (byte)('0' + outputDigit);
406 curDigit += 1;
408 // return the number of digits output
409 return (uint)(curDigit);
412 // round off the final digit
413 // default to rounding down if value got too close to 0
414 bool roundDown = low;
416 if (low == high) // is it legal to round up and down
418 // round to the closest digit by comparing value with 0.5.
420 // To do this we need to convert the inequality to large integer values.
421 // compare(value, 0.5)
422 // compare(scale * value, scale * 0.5)
423 // compare(2 * scale * value, scale)
424 scaledValue.Multiply(2);
425 int compare = BigInteger.Compare(ref scaledValue, ref scale);
426 roundDown = compare < 0;
428 // if we are directly in the middle, round towards the even digit (i.e. IEEE rouding rules)
429 if (compare == 0)
431 roundDown = (outputDigit & 1) == 0;
435 // print the rounded digit
436 if (roundDown)
438 buffer[curDigit] = (byte)('0' + outputDigit);
439 curDigit += 1;
441 else if (outputDigit == 9) // handle rounding up
443 // find the first non-nine prior digit
444 while (true)
446 // if we are at the first digit
447 if (curDigit == 0)
449 // output 1 at the next highest exponent
451 buffer[curDigit] = (byte)('1');
452 curDigit += 1;
453 decimalExponent += 1;
455 break;
458 curDigit -= 1;
460 if (buffer[curDigit] != '9')
462 // increment the digit
464 buffer[curDigit] += 1;
465 curDigit += 1;
467 break;
471 else
473 // values in the range [0,8] can perform a simple round up
474 buffer[curDigit] = (byte)('0' + outputDigit + 1);
475 curDigit += 1;
478 // return the number of digits output
479 uint outputLen = (uint)(curDigit);
480 Debug.Assert(outputLen <= buffer.Length);
481 return outputLen;