More Corelib cleanup (dotnet/coreclr#26872)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Buffers / Text / Utf8Parser / Utf8Parser.Number.cs
blob0551f6a8bd11a8cd42cf46d046677501b7ec4276
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;
7 namespace System.Buffers.Text
9 public static partial class Utf8Parser
11 [Flags]
12 private enum ParseNumberOptions
14 AllowExponent = 0x00000001,
17 private static bool TryParseNumber(ReadOnlySpan<byte> source, ref Number.NumberBuffer number, out int bytesConsumed, ParseNumberOptions options, out bool textUsedExponentNotation)
19 Debug.Assert(number.DigitsCount == 0);
20 Debug.Assert(number.Scale == 0);
21 Debug.Assert(!number.IsNegative);
22 Debug.Assert(!number.HasNonZeroTail);
24 number.CheckConsistency();
25 textUsedExponentNotation = false;
27 if (source.Length == 0)
29 bytesConsumed = 0;
30 return false;
33 Span<byte> digits = number.Digits;
35 int srcIndex = 0;
36 int dstIndex = 0;
38 // Consume the leading sign if any.
39 byte c = source[srcIndex];
40 switch (c)
42 case Utf8Constants.Minus:
43 number.IsNegative = true;
44 goto case Utf8Constants.Plus;
46 case Utf8Constants.Plus:
47 srcIndex++;
48 if (srcIndex == source.Length)
50 bytesConsumed = 0;
51 return false;
53 c = source[srcIndex];
54 break;
56 default:
57 break;
60 int startIndexDigitsBeforeDecimal = srcIndex;
61 int digitCount = 0;
62 int maxDigitCount = digits.Length - 1;
64 // Throw away any leading zeroes
65 while (srcIndex != source.Length)
67 c = source[srcIndex];
68 if (c != '0')
69 break;
70 srcIndex++;
73 if (srcIndex == source.Length)
75 bytesConsumed = srcIndex;
76 number.CheckConsistency();
77 return true;
80 int startIndexNonLeadingDigitsBeforeDecimal = srcIndex;
82 int hasNonZeroTail = 0;
83 while (srcIndex != source.Length)
85 c = source[srcIndex];
86 int value = (byte)(c - (byte)('0'));
88 if (value > 9)
90 break;
93 srcIndex++;
94 digitCount++;
96 if (digitCount >= maxDigitCount)
98 // For decimal and binary floating-point numbers, we only
99 // need to store digits up to maxDigCount. However, we still
100 // need to keep track of whether any additional digits past
101 // maxDigCount were non-zero, as that can impact rounding
102 // for an input that falls evenly between two representable
103 // results.
105 hasNonZeroTail |= value;
108 number.HasNonZeroTail = (hasNonZeroTail != 0);
110 int numDigitsBeforeDecimal = srcIndex - startIndexDigitsBeforeDecimal;
111 int numNonLeadingDigitsBeforeDecimal = srcIndex - startIndexNonLeadingDigitsBeforeDecimal;
113 Debug.Assert(dstIndex == 0);
114 int numNonLeadingDigitsBeforeDecimalToCopy = Math.Min(numNonLeadingDigitsBeforeDecimal, maxDigitCount);
115 source.Slice(startIndexNonLeadingDigitsBeforeDecimal, numNonLeadingDigitsBeforeDecimalToCopy).CopyTo(digits);
116 dstIndex = numNonLeadingDigitsBeforeDecimalToCopy;
117 number.Scale = numNonLeadingDigitsBeforeDecimal;
119 if (srcIndex == source.Length)
121 digits[dstIndex] = 0;
122 number.DigitsCount = dstIndex;
123 bytesConsumed = srcIndex;
124 number.CheckConsistency();
125 return true;
128 int numDigitsAfterDecimal = 0;
129 if (c == Utf8Constants.Period)
132 // Parse the digits after the decimal point.
135 srcIndex++;
136 int startIndexDigitsAfterDecimal = srcIndex;
138 while (srcIndex != source.Length)
140 c = source[srcIndex];
141 int value = (byte)(c - (byte)('0'));
143 if (value > 9)
145 break;
148 srcIndex++;
149 digitCount++;
151 if (digitCount >= maxDigitCount)
153 // For decimal and binary floating-point numbers, we only
154 // need to store digits up to maxDigCount. However, we still
155 // need to keep track of whether any additional digits past
156 // maxDigCount were non-zero, as that can impact rounding
157 // for an input that falls evenly between two representable
158 // results.
160 hasNonZeroTail |= value;
163 number.HasNonZeroTail = (hasNonZeroTail != 0);
165 numDigitsAfterDecimal = srcIndex - startIndexDigitsAfterDecimal;
167 int startIndexOfDigitsAfterDecimalToCopy = startIndexDigitsAfterDecimal;
168 if (dstIndex == 0)
170 // Not copied any digits to the Number struct yet. This means we must continue discarding leading zeroes even though they appeared after the decimal point.
171 while (startIndexOfDigitsAfterDecimalToCopy < srcIndex && source[startIndexOfDigitsAfterDecimalToCopy] == '0')
173 number.Scale--;
174 startIndexOfDigitsAfterDecimalToCopy++;
178 int numDigitsAfterDecimalToCopy = Math.Min(srcIndex - startIndexOfDigitsAfterDecimalToCopy, maxDigitCount - dstIndex);
179 source.Slice(startIndexOfDigitsAfterDecimalToCopy, numDigitsAfterDecimalToCopy).CopyTo(digits.Slice(dstIndex));
180 dstIndex += numDigitsAfterDecimalToCopy;
181 // We "should" really NUL terminate, but there are multiple places we'd have to do this and it is a precondition that the caller pass in a fully zero=initialized Number.
183 if (srcIndex == source.Length)
185 if (numDigitsBeforeDecimal == 0 && numDigitsAfterDecimal == 0)
187 // For compatibility. You can say "5." and ".5" but you can't say "."
188 bytesConsumed = 0;
189 return false;
192 digits[dstIndex] = 0;
193 number.DigitsCount = dstIndex;
194 bytesConsumed = srcIndex;
195 number.CheckConsistency();
196 return true;
200 if (numDigitsBeforeDecimal == 0 && numDigitsAfterDecimal == 0)
202 bytesConsumed = 0;
203 return false;
206 if ((c & ~0x20u) != 'E')
208 digits[dstIndex] = 0;
209 number.DigitsCount = dstIndex;
210 bytesConsumed = srcIndex;
211 number.CheckConsistency();
212 return true;
216 // Parse the exponent after the "E"
218 textUsedExponentNotation = true;
219 srcIndex++;
221 if ((options & ParseNumberOptions.AllowExponent) == 0)
223 bytesConsumed = 0;
224 return false;
227 if (srcIndex == source.Length)
229 bytesConsumed = 0;
230 return false;
233 bool exponentIsNegative = false;
234 c = source[srcIndex];
235 switch (c)
237 case Utf8Constants.Minus:
238 exponentIsNegative = true;
239 goto case Utf8Constants.Plus;
241 case Utf8Constants.Plus:
242 srcIndex++;
243 if (srcIndex == source.Length)
245 bytesConsumed = 0;
246 return false;
248 c = source[srcIndex];
249 break;
251 default:
252 break;
255 // If the next character isn't a digit, an exponent wasn't specified
256 if ((byte)(c - (byte)('0')) > 9)
258 bytesConsumed = 0;
259 return false;
262 if (!TryParseUInt32D(source.Slice(srcIndex), out uint absoluteExponent, out int bytesConsumedByExponent))
264 // Since we found at least one digit, we know that any failure to parse means we had an
265 // exponent that was larger than uint.MaxValue, and we can just eat characters until the end
266 absoluteExponent = uint.MaxValue;
268 // This also means that we know there was at least 10 characters and we can "eat" those, and
269 // continue eating digits from there
270 srcIndex += 10;
272 while (srcIndex != source.Length)
274 c = source[srcIndex];
275 int value = (byte)(c - (byte)('0'));
277 if (value > 9)
279 break;
282 srcIndex++;
286 srcIndex += bytesConsumedByExponent;
288 if (exponentIsNegative)
290 if (number.Scale < int.MinValue + (long)absoluteExponent)
292 // A scale underflow means all non-zero digits are all so far to the right of the decimal point, no
293 // number format we have will be able to see them. Just pin the scale at the absolute minimum
294 // and let the converter produce a 0 with the max precision available for that type.
295 number.Scale = int.MinValue;
297 else
299 number.Scale -= (int)absoluteExponent;
302 else
304 if (number.Scale > int.MaxValue - (long)absoluteExponent)
306 // A scale overflow means all non-zero digits are all so far to the right of the decimal point, no
307 // number format we have will be able to see them. Just pin the scale at the absolute maximum
308 // and let the converter produce a 0 with the max precision available for that type.
309 number.Scale = int.MaxValue;
311 else
313 number.Scale += (int)absoluteExponent;
317 digits[dstIndex] = 0;
318 number.DigitsCount = dstIndex;
319 bytesConsumed = srcIndex;
320 number.CheckConsistency();
321 return true;