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
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)
33 Span
<byte> digits
= number
.Digits
;
38 // Consume the leading sign if any.
39 byte c
= source
[srcIndex
];
42 case Utf8Constants
.Minus
:
43 number
.IsNegative
= true;
44 goto case Utf8Constants
.Plus
;
46 case Utf8Constants
.Plus
:
48 if (srcIndex
== source
.Length
)
60 int startIndexDigitsBeforeDecimal
= srcIndex
;
62 int maxDigitCount
= digits
.Length
- 1;
64 // Throw away any leading zeroes
65 while (srcIndex
!= source
.Length
)
73 if (srcIndex
== source
.Length
)
75 bytesConsumed
= srcIndex
;
76 number
.CheckConsistency();
80 int startIndexNonLeadingDigitsBeforeDecimal
= srcIndex
;
82 int hasNonZeroTail
= 0;
83 while (srcIndex
!= source
.Length
)
86 int value = (byte)(c
- (byte)('0'));
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
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();
128 int numDigitsAfterDecimal
= 0;
129 if (c
== Utf8Constants
.Period
)
132 // Parse the digits after the decimal point.
136 int startIndexDigitsAfterDecimal
= srcIndex
;
138 while (srcIndex
!= source
.Length
)
140 c
= source
[srcIndex
];
141 int value = (byte)(c
- (byte)('0'));
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
160 hasNonZeroTail
|= value;
163 number
.HasNonZeroTail
= (hasNonZeroTail
!= 0);
165 numDigitsAfterDecimal
= srcIndex
- startIndexDigitsAfterDecimal
;
167 int startIndexOfDigitsAfterDecimalToCopy
= startIndexDigitsAfterDecimal
;
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')
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 "."
192 digits
[dstIndex
] = 0;
193 number
.DigitsCount
= dstIndex
;
194 bytesConsumed
= srcIndex
;
195 number
.CheckConsistency();
200 if (numDigitsBeforeDecimal
== 0 && numDigitsAfterDecimal
== 0)
206 if ((c
& ~
0x20u
) != 'E')
208 digits
[dstIndex
] = 0;
209 number
.DigitsCount
= dstIndex
;
210 bytesConsumed
= srcIndex
;
211 number
.CheckConsistency();
216 // Parse the exponent after the "E"
218 textUsedExponentNotation
= true;
221 if ((options
& ParseNumberOptions
.AllowExponent
) == 0)
227 if (srcIndex
== source
.Length
)
233 bool exponentIsNegative
= false;
234 c
= source
[srcIndex
];
237 case Utf8Constants
.Minus
:
238 exponentIsNegative
= true;
239 goto case Utf8Constants
.Plus
;
241 case Utf8Constants
.Plus
:
243 if (srcIndex
== source
.Length
)
248 c
= source
[srcIndex
];
255 // If the next character isn't a digit, an exponent wasn't specified
256 if ((byte)(c
- (byte)('0')) > 9)
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
272 while (srcIndex
!= source
.Length
)
274 c
= source
[srcIndex
];
275 int value = (byte)(c
- (byte)('0'));
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
;
299 number
.Scale
-= (int)absoluteExponent
;
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
;
313 number
.Scale
+= (int)absoluteExponent
;
317 digits
[dstIndex
] = 0;
318 number
.DigitsCount
= dstIndex
;
319 bytesConsumed
= srcIndex
;
320 number
.CheckConsistency();