// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Numerics; using System.Runtime.CompilerServices; using System.Text; using System.Text.Unicode; namespace System { // The Parse methods provided by the numeric classes convert a // string to a numeric value. The optional style parameter specifies the // permitted style of the numeric string. It must be a combination of bit flags // from the NumberStyles enumeration. The optional info parameter // specifies the NumberFormatInfo instance to use when parsing the // string. If the info parameter is null or omitted, the numeric // formatting information is obtained from the current culture. // // Numeric strings produced by the Format methods using the Currency, // Decimal, Engineering, Fixed point, General, or Number standard formats // (the C, D, E, F, G, and N format specifiers) are guaranteed to be parsable // by the Parse methods if the NumberStyles.Any style is // specified. Note, however, that the Parse methods do not accept // NaNs or Infinities. internal interface IBinaryIntegerParseAndFormatInfo<TSelf> : IBinaryInteger<TSelf>, IMinMaxValue<TSelf> where TSelf : unmanaged, IBinaryIntegerParseAndFormatInfo<TSelf> { static abstract bool IsSigned { get; } static abstract int MaxDigitCount { get; } static abstract int MaxHexDigitCount { get; } static abstract TSelf MaxValueDiv10 { get; } static abstract string OverflowMessage { get; } static abstract bool IsGreaterThanAsUnsigned(TSelf left, TSelf right); static abstract TSelf MultiplyBy10(TSelf value); static abstract TSelf MultiplyBy16(TSelf value); } internal interface IBinaryFloatParseAndFormatInfo<TSelf> : IBinaryFloatingPointIeee754<TSelf>, IMinMaxValue<TSelf> where TSelf : unmanaged, IBinaryFloatParseAndFormatInfo<TSelf> { /// <summary> /// Ceiling(Log10(5^(Abs(MinBinaryExponent) - 1))) + NormalMantissaBits + 1 + 1 /// </summary> static abstract int NumberBufferLength { get; } static abstract ulong ZeroBits { get; } static abstract ulong InfinityBits { get; } static abstract ulong NormalMantissaMask { get; } static abstract ulong DenormalMantissaMask { get; } static abstract int MinBinaryExponent { get; } static abstract int MaxBinaryExponent { get; } /// <summary> /// Floor(Log10(Epsilon)) /// </summary> static abstract int MinDecimalExponent { get; } /// <summary> /// Ceiling(Log10(MaxValue)) /// </summary> static abstract int MaxDecimalExponent { get; } static abstract int ExponentBias { get; } static abstract ushort ExponentBits { get; } static abstract int OverflowDecimalExponent { get; } static abstract int InfinityExponent { get; } static abstract ushort NormalMantissaBits { get; } static abstract ushort DenormalMantissaBits { get; } /// <summary> /// Ceiling(Log10(2^(MinBinaryExponent - 1 - DenormalMantissaBits - 64))) /// </summary> static abstract int MinFastFloatDecimalExponent { get; } /// <summary> /// MaxDecimalExponent - 1 /// </summary> static abstract int MaxFastFloatDecimalExponent { get; } /// <summary> /// -Floor(Log5(2^(64 - NormalMantissaBits))) /// </summary> static abstract int MinExponentRoundToEven { get; } /// <summary> /// Floor(Log5(2^(NormalMantissaBits + 1))) /// </summary> static abstract int MaxExponentRoundToEven { get; } /// <summary> /// Max(n) when 10^n can be precisely represented /// </summary> static abstract int MaxExponentFastPath { get; } static abstract ulong MaxMantissaFastPath { get; } static abstract TSelf BitsToFloat(ulong bits); static abstract ulong FloatToBits(TSelf value); /// <summary> /// Maximum number of digits required to guarantee that any given floating point /// number can roundtrip. Some numbers may require less, but none will require more. /// </summary> /// <remarks> /// Ceiling(Log10(2^NormalMantissaBits)) + 1 /// </remarks> static abstract int MaxRoundTripDigits { get; } /// <summary> /// MaxPrecisionCustomFormat is used to ensure that /// custom format strings return the same string as in previous releases when the format /// would return x digits or less (where x is the value of the corresponding constant). /// In order to support more digits, we would need to update ParseFormatSpecifier to pre-parse /// the format and determine exactly how many digits are being requested and whether they /// represent "significant digits" or "digits after the decimal point". /// </summary> static abstract int MaxPrecisionCustomFormat { get; } } internal interface IDecimalIeee754ParseAndFormatInfo<TSelf, TValue> where TSelf : unmanaged, IDecimalIeee754ParseAndFormatInfo<TSelf, TValue> where TValue : unmanaged, IBinaryInteger<TValue> { static abstract int Precision { get; } static abstract int BufferLength { get; } static abstract int MaxExponent { get; } static abstract int MinExponent { get; } static virtual int MaxAdjustedExponent => TSelf.MaxExponent - TSelf.Precision + 1; static virtual int MinAdjustedExponent => TSelf.MinExponent - TSelf.Precision + 1; static abstract int ExponentBias { get; } static abstract TValue PositiveInfinity { get; } static abstract TValue NegativeInfinity { get; } static abstract TValue NaN { get; } static abstract TValue Zero { get; } static abstract TValue MaxSignificand { get; } static abstract TValue NumberToSignificand(ref Number.NumberBuffer number, int digits); static abstract string ToDecStr(TValue significand); static abstract int ConvertToExponent(TValue value); static abstract TValue Power10(int exponent); static abstract (TValue Quotient, TValue Remainder) DivRemPow10(TValue value, int exponent); static abstract TSelf Construct(TValue value); static abstract int CountDigits(TValue significand); static abstract int NumberBitsSignificand { get; } static abstract TValue NaNMask { get; } static abstract TValue SNaNMask { get; } static abstract TValue NaNPayloadMask { get; } static abstract TValue SignMask { get; } static abstract TValue G0G1Mask { get; } static abstract TValue G0ToGwPlus1ExponentMask { get; } //G0 to G(w+1) static abstract TValue G2ToGwPlus3ExponentMask { get; } //G2 to G(w+3) static abstract TValue GwPlus2ToGwPlus4SignificandMask { get; } //G(w+2) to G(w+4) static abstract TValue GwPlus4SignificandMask { get; } //G(w+4) static abstract TValue MostSignificantBitOfSignificandMask { get; } static abstract bool IsNaN(TValue decimalBits); static abstract bool IsFinite(TValue decimalBits); static abstract bool IsInfinity(TValue decimalBits); static abstract bool IsPositiveInfinity(TValue decimalBits); static abstract bool IsNegativeInfinity(TValue decimalBits); static abstract bool IsNegative(TValue decimalBits); static abstract TValue EncodeExponentToG0ThroughGwPlus1(uint biasedExponent); static abstract TValue EncodeExponentToG2ThroughGwPlus3(uint biasedExponent); } internal static partial class Number { private const int Int32Precision = 10; private const int UInt32Precision = Int32Precision; private const int Int64Precision = 19; private const int UInt64Precision = 20; private const int Int128Precision = 39; private const int UInt128Precision = 39; private const int FloatingPointMaxExponent = 309; private const int FloatingPointMinExponent = -324; private const int FloatingPointMaxDenormalMantissaBits = 52; private static unsafe bool TryNumberBufferToBinaryInteger<TInteger>(ref NumberBuffer number, ref TInteger value) where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo<TInteger> { number.CheckConsistency(); int i = number.Scale; if ((i > TInteger.MaxDigitCount) || (i < number.DigitsCount) || (!TInteger.IsSigned && number.IsNegative) || number.HasNonZeroTail) { return false; } byte* p = number.DigitsPtr; Debug.Assert(p != null); TInteger n = TInteger.Zero; while (--i >= 0) { if (TInteger.IsGreaterThanAsUnsigned(n, TInteger.MaxValueDiv10)) { return false; } n = TInteger.MultiplyBy10(n); if (*p != '\0') { TInteger newN = n + TInteger.CreateTruncating(*p++ - '0'); if (!TInteger.IsSigned && (newN < n)) { return false; } n = newN; } } if (TInteger.IsSigned) { if (number.IsNegative) { n = -n; if (n > TInteger.Zero) { return false; } } else if (n < TInteger.Zero) { return false; } } value = n; return true; } internal static TInteger ParseBinaryInteger<TChar, TInteger>(ReadOnlySpan<TChar> value, NumberStyles styles, NumberFormatInfo info) where TChar : unmanaged, IUtfChar<TChar> where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo<TInteger> { ParsingStatus status = TryParseBinaryInteger(value, styles, info, out TInteger result, out _); if (status != ParsingStatus.OK) { ThrowOverflowOrFormatException<TChar, TInteger>(status, value); } return result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ParsingStatus TryParseBinaryInteger<TChar, TInteger>(ReadOnlySpan<TChar> value, NumberStyles styles, NumberFormatInfo info, out TInteger result, out int elementsConsumed) where TChar : unmanaged, IUtfChar<TChar> where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo<TInteger> { if ((styles & ~(NumberStyles.Integer | AllowTrailingInvalidCharacters)) == 0) { // Optimized path for the common case of anything that's allowed for integer style. return TryParseBinaryIntegerStyle(value, styles, info, out result, out elementsConsumed); } if ((styles & NumberStyles.AllowHexSpecifier) != 0) { return TryParseBinaryIntegerHexOrBinaryNumberStyle<TChar, TInteger, HexParser<TInteger>>(value, styles, out result, out elementsConsumed); } if ((styles & NumberStyles.AllowBinarySpecifier) != 0) { return TryParseBinaryIntegerHexOrBinaryNumberStyle<TChar, TInteger, BinaryParser<TInteger>>(value, styles, out result, out elementsConsumed); } return TryParseBinaryIntegerNumber(value, styles, info, out result, out elementsConsumed); } private static ParsingStatus TryParseBinaryIntegerNumber<TChar, TInteger>(ReadOnlySpan<TChar> value, NumberStyles styles, NumberFormatInfo info, out TInteger result, out int elementsConsumed) where TChar : unmanaged, IUtfChar<TChar> where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo<TInteger> { result = TInteger.Zero; NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, stackalloc byte[TInteger.MaxDigitCount + 1]); if (!TryStringToNumber(value, styles, ref number, info, out elementsConsumed)) { return ParsingStatus.Failed; } if (!TryNumberBufferToBinaryInteger(ref number, ref result)) { elementsConsumed = 0; return ParsingStatus.Overflow; } return ParsingStatus.OK; } /// <summary>Parses int limited to styles that make up NumberStyles.Integer.</summary> internal static ParsingStatus TryParseBinaryIntegerStyle<TChar, TInteger>(ReadOnlySpan<TChar> value, NumberStyles styles, NumberFormatInfo info, out TInteger result, out int elementsConsumed) where TChar : unmanaged, IUtfChar<TChar> where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo<TInteger> { Debug.Assert((styles & ~(NumberStyles.Integer | AllowTrailingInvalidCharacters)) == 0, "Only handles subsets of Integer format"); if (value.IsEmpty) { goto FalseExit; } int index = 0; uint num = TChar.CastToUInt32(value[0]); // Skip past any whitespace at the beginning. if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num)) { do { index++; if ((uint)index >= (uint)value.Length) { goto FalseExit; } num = TChar.CastToUInt32(value[index]); } while (IsWhite(num)); } // Parse leading sign. bool isNegative = false; if ((styles & NumberStyles.AllowLeadingSign) != 0) { if (info.HasInvariantNumberSigns) { if (num == '-') { isNegative = true; index++; if ((uint)index >= (uint)value.Length) { goto FalseExit; } num = TChar.CastToUInt32(value[index]); } else if (num == '+') { index++; if ((uint)index >= (uint)value.Length) { goto FalseExit; } num = TChar.CastToUInt32(value[index]); } } else if (info.AllowHyphenDuringParsing() && num == '-') { isNegative = true; index++; if ((uint)index >= (uint)value.Length) { goto FalseExit; } num = TChar.CastToUInt32(value[index]); } else { // Slice a copy rather than reassigning value, so that index (and thus the number // of elements reported as consumed) stays relative to the original input. ReadOnlySpan<TChar> remaining = value.Slice(index); ReadOnlySpan<TChar> positiveSign = info.PositiveSignTChar<TChar>(); ReadOnlySpan<TChar> negativeSign = info.NegativeSignTChar<TChar>(); if (!positiveSign.IsEmpty && remaining.StartsWith(positiveSign)) { index += positiveSign.Length; if ((uint)index >= (uint)value.Length) { goto FalseExit; } num = TChar.CastToUInt32(value[index]); } else if (!negativeSign.IsEmpty && remaining.StartsWith(negativeSign)) { isNegative = true; index += negativeSign.Length; if ((uint)index >= (uint)value.Length) { goto FalseExit; } num = TChar.CastToUInt32(value[index]); } } } bool overflow = !TInteger.IsSigned && isNegative; TInteger answer = TInteger.Zero; if (IsDigit(num)) { // Skip past leading zeros. if (num == '0') { do { index++; if ((uint)index >= (uint)value.Length) { goto DoneAtEnd; } num = TChar.CastToUInt32(value[index]); } while (num == '0'); if (!IsDigit(num)) { if (!TInteger.IsSigned) { overflow = false; } goto HasTrailingChars; } } // Parse most digits, up to the potential for overflow, which can't happen until after MaxDigitCount - 1 digits. answer = TInteger.CreateTruncating(num - '0'); // first digit index++; for (int i = 0; i < TInteger.MaxDigitCount - 2; i++) // next MaxDigitCount - 2 digits can't overflow { if ((uint)index >= (uint)value.Length) { if (!TInteger.IsSigned) { goto DoneAtEndButPotentialOverflow; } else { goto DoneAtEnd; } } num = TChar.CastToUInt32(value[index]); if (!IsDigit(num)) { goto HasTrailingChars; } index++; answer = TInteger.MultiplyBy10(answer); answer += TInteger.CreateTruncating(num - '0'); } if ((uint)index >= (uint)value.Length) { if (!TInteger.IsSigned) { goto DoneAtEndButPotentialOverflow; } else { goto DoneAtEnd; } } num = TChar.CastToUInt32(value[index]); if (!IsDigit(num)) { goto HasTrailingChars; } index++; // Potential overflow now processing the MaxDigitCount digit. if (!TInteger.IsSigned) { overflow |= (answer > TInteger.MaxValueDiv10) || ((answer == TInteger.MaxValueDiv10) && (num > '5')); } else { overflow = answer > TInteger.MaxValueDiv10; } answer = TInteger.MultiplyBy10(answer); answer += TInteger.CreateTruncating(num - '0'); if (TInteger.IsSigned) { overflow |= TInteger.IsGreaterThanAsUnsigned(answer, TInteger.MaxValue + (isNegative ? TInteger.One : TInteger.Zero)); } if ((uint)index >= (uint)value.Length) { goto DoneAtEndButPotentialOverflow; } // At this point, we're either overflowing or hitting a formatting error. // Format errors take precedence for compatibility. num = TChar.CastToUInt32(value[index]); while (IsDigit(num)) { overflow = true; index++; if ((uint)index >= (uint)value.Length) { goto OverflowExit; } num = TChar.CastToUInt32(value[index]); } goto HasTrailingChars; } goto FalseExit; DoneAtEndButPotentialOverflow: if (overflow) { goto OverflowExit; } DoneAtEnd: if (!TInteger.IsSigned) { result = answer; } else { result = isNegative ? -answer : answer; } ParsingStatus status = ParsingStatus.OK; elementsConsumed = index; Exit: return status; InvalidExit: // For compatibility we still need to process any trailing // nulls that exist and report them as having been consumed. index = ConsumeTrailingNulls(value, index); if ((index == value.Length) || ((styles & AllowTrailingInvalidCharacters) != 0)) { goto DoneAtEndButPotentialOverflow; } FalseExit: // parsing failed result = TInteger.Zero; elementsConsumed = 0; status = ParsingStatus.Failed; goto Exit; OverflowExit: result = TInteger.Zero; elementsConsumed = 0; status = ParsingStatus.Overflow; goto Exit; HasTrailingChars: // we've successfully parsed, but there are still remaining characters in the span // Skip past trailing whitespace, then past trailing zeros, and if anything else remains, fail. if (IsWhite(num)) { if ((styles & NumberStyles.AllowTrailingWhite) == 0) { goto InvalidExit; } for (index++; index < value.Length; index++) { uint ch = TChar.CastToUInt32(value[index]); if (!IsWhite(ch)) { break; } } if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; } goto InvalidExit; } internal interface IHexOrBinaryParser<TInteger> where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo<TInteger> { static abstract NumberStyles AllowedStyles { get; } static abstract bool IsValidChar(uint ch); static abstract uint FromChar(uint ch); static abstract uint MaxDigitValue { get; } static abstract int MaxDigitCount { get; } static abstract TInteger ShiftLeftForNextDigit(TInteger value); } internal readonly struct HexParser<TInteger> : IHexOrBinaryParser<TInteger> where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo<TInteger> { public static NumberStyles AllowedStyles => NumberStyles.HexNumber | AllowTrailingInvalidCharacters; public static bool IsValidChar(uint ch) => HexConverter.IsHexChar((int)ch); public static uint FromChar(uint ch) => (uint)HexConverter.FromChar((int)ch); public static uint MaxDigitValue => 0xF; public static int MaxDigitCount => TInteger.MaxHexDigitCount; public static TInteger ShiftLeftForNextDigit(TInteger value) => TInteger.MultiplyBy16(value); } private readonly struct BinaryParser<TInteger> : IHexOrBinaryParser<TInteger> where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo<TInteger> { public static NumberStyles AllowedStyles => NumberStyles.BinaryNumber | AllowTrailingInvalidCharacters; public static bool IsValidChar(uint ch) => (ch - '0') <= 1; public static uint FromChar(uint ch) => ch - '0'; public static uint MaxDigitValue => 1; public static unsafe int MaxDigitCount => sizeof(TInteger) * 8; public static TInteger ShiftLeftForNextDigit(TInteger value) => value << 1; } internal static ParsingStatus TryParseBinaryIntegerHexOrBinaryNumberStyle<TChar, TInteger, TParser>(ReadOnlySpan<TChar> value, NumberStyles styles, out TInteger result, out int elementsConsumed) where TChar : unmanaged, IUtfChar<TChar> where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo<TInteger> where TParser : struct, IHexOrBinaryParser<TInteger> { Debug.Assert((styles & ~TParser.AllowedStyles) == 0, $"Only handles subsets of {TParser.AllowedStyles} format"); if (value.IsEmpty) { goto FalseExit; } int index = 0; uint num = TChar.CastToUInt32(value[0]); // Skip past any whitespace at the beginning. if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num)) { do { index++; if ((uint)index >= (uint)value.Length) { goto FalseExit; } num = TChar.CastToUInt32(value[index]); } while (IsWhite(num)); } bool overflow = false; TInteger answer = TInteger.Zero; if (TParser.IsValidChar(num)) { // Skip past leading zeros. if (num == '0') { do { index++; if ((uint)index >= (uint)value.Length) { goto DoneAtEnd; } num = TChar.CastToUInt32(value[index]); } while (num == '0'); if (!TParser.IsValidChar(num)) { goto HasTrailingChars; } } // Parse up through MaxDigitCount digits, as no overflow is possible answer = TInteger.CreateTruncating(TParser.FromChar(num)); // first digit index++; for (int i = 0; i < TParser.MaxDigitCount - 1; i++) // next MaxDigitCount - 1 digits can't overflow { if ((uint)index >= (uint)value.Length) { goto DoneAtEnd; } num = TChar.CastToUInt32(value[index]); uint numValue = TParser.FromChar(num); if (numValue > TParser.MaxDigitValue) { goto HasTrailingChars; } index++; answer = TParser.ShiftLeftForNextDigit(answer); answer += TInteger.CreateTruncating(numValue); } // If there's another digit, it's an overflow. if ((uint)index >= (uint)value.Length) { goto DoneAtEnd; } num = TChar.CastToUInt32(value[index]); if (!TParser.IsValidChar(num)) { goto HasTrailingChars; } // At this point, we're either overflowing or hitting a formatting error. // Format errors take precedence for compatibility. Read through any remaining digits. do { index++; if ((uint)index >= (uint)value.Length) { goto OverflowExit; } num = TChar.CastToUInt32(value[index]); } while (TParser.IsValidChar(num)); overflow = true; goto HasTrailingChars; } goto FalseExit; DoneAtEndButPotentialOverflow: if (overflow) { goto OverflowExit; } DoneAtEnd: result = answer; ParsingStatus status = ParsingStatus.OK; elementsConsumed = index; Exit: return status; InvalidExit: // For compatibility we still need to process any trailing // nulls that exist and report them as having been consumed. index = ConsumeTrailingNulls(value, index); if ((index == value.Length) || ((styles & AllowTrailingInvalidCharacters) != 0)) { goto DoneAtEndButPotentialOverflow; } FalseExit: // parsing failed result = TInteger.Zero; elementsConsumed = 0; status = ParsingStatus.Failed; goto Exit; OverflowExit: result = TInteger.Zero; elementsConsumed = 0; status = ParsingStatus.Overflow; goto Exit; HasTrailingChars: // we've successfully parsed, but there are still remaining characters in the span // Skip past trailing whitespace, then past trailing zeros, and if anything else remains, fail. if (IsWhite(num)) { if ((styles & NumberStyles.AllowTrailingWhite) == 0) { goto InvalidExit; } for (index++; index < value.Length; index++) { uint ch = TChar.CastToUInt32(value[index]); if (!IsWhite(ch)) { break; } } if ((uint)index >= (uint)value.Length) { goto DoneAtEndButPotentialOverflow; } } goto InvalidExit; } internal static decimal ParseDecimal<TChar>(ReadOnlySpan<TChar> value, NumberStyles styles, NumberFormatInfo info) where TChar : unmanaged, IUtfChar<TChar> { ParsingStatus status = TryParseDecimal(value, styles, info, out decimal result, out _); if (status != ParsingStatus.OK) { if (status == ParsingStatus.Failed) { ThrowFormatException(value); } ThrowDecimalOverflowException(); } return result; } internal static TDecimal ParseDecimalIeee754<TChar, TDecimal, TValue>(ReadOnlySpan<TChar> value, NumberStyles styles, NumberFormatInfo info) where TChar : unmanaged, IUtfChar<TChar> where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo<TDecimal, TValue> where TValue : unmanaged, IBinaryInteger<TValue> { ParsingStatus status = TryParseDecimalIeee754<TChar, TDecimal, TValue>(value, styles, info, out TDecimal result, out _); if (status == ParsingStatus.Failed) { ThrowFormatException(value); } return result; } internal static unsafe bool TryNumberToDecimal(ref NumberBuffer number, ref decimal value) { number.CheckConsistency(); byte* p = number.DigitsPtr; int e = number.Scale; bool sign = number.IsNegative; uint c = *p; if (c == 0) { // To avoid risking an app-compat issue with pre 4.5 (where some app was illegally using Reflection to examine the internal scale bits), we'll only force // the scale to 0 if the scale was previously positive (previously, such cases were unparsable to a bug.) value = new decimal(0, 0, 0, sign, (byte)Math.Clamp(-e, 0, 28)); return true; } if (e > DecimalPrecision) return false; ulong low64 = 0; while (e > -28) { e--; low64 *= 10; low64 += c - '0'; c = *++p; if (low64 >= ulong.MaxValue / 10) break; if (c == 0) { while (e > 0) { e--; low64 *= 10; if (low64 >= ulong.MaxValue / 10) break; } break; } } uint high = 0; while ((e > 0 || (c != 0 && e > -28)) && (high < uint.MaxValue / 10 || (high == uint.MaxValue / 10 && (low64 < 0x99999999_99999999 || (low64 == 0x99999999_99999999 && c <= '5'))))) { // multiply by 10 ulong tmpLow = (uint)low64 * 10UL; ulong tmp64 = ((uint)(low64 >> 32) * 10UL) + (tmpLow >> 32); low64 = (uint)tmpLow + (tmp64 << 32); high = (uint)(tmp64 >> 32) + (high * 10); if (c != 0) { c -= '0'; low64 += c; if (low64 < c) high++; c = *++p; } e--; } if (c >= '5') { if ((c == '5') && ((low64 & 1) == 0)) { c = *++p; bool hasZeroTail = !number.HasNonZeroTail; // We might still have some additional digits, in which case they need // to be considered as part of hasZeroTail. Some examples of this are: // * 3.0500000000000000000001e-27 // * 3.05000000000000000000001e-27 // In these cases, we will have processed 3 and 0, and ended on 5. The // buffer, however, will still contain a number of trailing zeros and // a trailing non-zero number. while ((c != 0) && hasZeroTail) { hasZeroTail &= c == '0'; c = *++p; } // We should either be at the end of the stream or have a non-zero tail Debug.Assert((c == 0) || !hasZeroTail); if (hasZeroTail) { // When the next digit is 5, the number is even, and all following // digits are zero we don't need to round. goto NoRounding; } } if (++low64 == 0 && ++high == 0) { low64 = 0x99999999_9999999A; high = uint.MaxValue / 10; e++; } } NoRounding: if (e > 0) return false; if (e <= -DecimalPrecision) { // Parsing a large scale zero can give you more precision than fits in the decimal. // This should only happen for actual zeros or very small numbers that round to zero. value = new decimal(0, 0, 0, sign, DecimalPrecision - 1); } else { value = new decimal((int)low64, (int)(low64 >> 32), (int)high, sign, (byte)-e); } return true; } internal static TFloat ParseFloat<TChar, TFloat>(ReadOnlySpan<TChar> value, NumberStyles styles, NumberFormatInfo info) where TChar : unmanaged, IUtfChar<TChar> where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo<TFloat> { if (!TryParseFloat(value, styles, info, out TFloat result, out _)) { ThrowFormatException(value); } return result; } internal static unsafe ParsingStatus TryParseDecimal<TChar>(ReadOnlySpan<TChar> value, NumberStyles styles, NumberFormatInfo info, out decimal result, out int elementsConsumed) where TChar : unmanaged, IUtfChar<TChar> { NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, stackalloc byte[DecimalNumberBufferLength]); result = 0; if (!TryStringToNumber(value, styles, ref number, info, out elementsConsumed)) { return ParsingStatus.Failed; } if (!TryNumberToDecimal(ref number, ref result)) { elementsConsumed = 0; return ParsingStatus.Overflow; } return ParsingStatus.OK; } internal static ParsingStatus TryParseDecimalIeee754<TChar, TDecimal, TValue>(ReadOnlySpan<TChar> value, NumberStyles styles, NumberFormatInfo info, out TDecimal result, out int elementsConsumed) where TChar : unmanaged, IUtfChar<TChar> where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo<TDecimal, TValue> where TValue : unmanaged, IBinaryInteger<TValue> { NumberBuffer number = new NumberBuffer(NumberBufferKind.DecimalIeee754, stackalloc byte[TDecimal.BufferLength]); result = default; if (!TryStringToNumber(value, styles, ref number, info, out elementsConsumed)) { // Leading and trailing whitespace around a special value (Infinity/NaN) is always // consumed, independent of the AllowLeadingWhite/AllowTrailingWhite styles (historical // precedent). When AllowTrailingInvalidCharacters is set, parsing additionally stops at // the first non-whitespace character after the symbol; otherwise such a trailing // character rejects the match. ReadOnlySpan<TChar> valueTrim = SpanTrimStart(value); bool allowTrailingInvalid = (styles & AllowTrailingInvalidCharacters) != 0; // elementsConsumed is seeded with the offset of the candidate within value (the leading // whitespace, plus any sign) and then advanced by TryMatchSpecialValueSymbol on a match. elementsConsumed = value.Length - valueTrim.Length; ReadOnlySpan<TChar> positiveInfinitySymbol = info.PositiveInfinitySymbolTChar<TChar>(); if (TryMatchSpecialValueSymbol(valueTrim, positiveInfinitySymbol, allowTrailingInvalid, ref elementsConsumed)) { result = TDecimal.Construct(TDecimal.PositiveInfinity); return ParsingStatus.OK; } if (TryMatchSpecialValueSymbol(valueTrim, info.NegativeInfinitySymbolTChar<TChar>(), allowTrailingInvalid, ref elementsConsumed)) { result = TDecimal.Construct(TDecimal.NegativeInfinity); return ParsingStatus.OK; } ReadOnlySpan<TChar> nanSymbol = info.NaNSymbolTChar<TChar>(); if (TryMatchSpecialValueSymbol(valueTrim, nanSymbol, allowTrailingInvalid, ref elementsConsumed)) { result = TDecimal.Construct(TDecimal.NaN); return ParsingStatus.OK; } ReadOnlySpan<TChar> positiveSign = info.PositiveSignTChar<TChar>(); if (SpanStartsWith(valueTrim, positiveSign, StringComparison.OrdinalIgnoreCase)) { ReadOnlySpan<TChar> afterSign = valueTrim.Slice(positiveSign.Length); elementsConsumed = value.Length - afterSign.Length; if (TryMatchSpecialValueSymbol(afterSign, positiveInfinitySymbol, allowTrailingInvalid, ref elementsConsumed)) { result = TDecimal.Construct(TDecimal.PositiveInfinity); return ParsingStatus.OK; } else if (TryMatchSpecialValueSymbol(afterSign, nanSymbol, allowTrailingInvalid, ref elementsConsumed)) { result = TDecimal.Construct(TDecimal.NaN); return ParsingStatus.OK; } result = TDecimal.Construct(TDecimal.Zero); elementsConsumed = 0; return ParsingStatus.Failed; } ReadOnlySpan<TChar> negativeSign = info.NegativeSignTChar<TChar>(); if (SpanStartsWith(valueTrim, negativeSign, StringComparison.OrdinalIgnoreCase)) { ReadOnlySpan<TChar> afterSign = valueTrim.Slice(negativeSign.Length); elementsConsumed = value.Length - afterSign.Length; if (TryMatchSpecialValueSymbol(afterSign, nanSymbol, allowTrailingInvalid, ref elementsConsumed)) { result = TDecimal.Construct(TDecimal.NaN); return ParsingStatus.OK; } if (info.AllowHyphenDuringParsing() && SpanStartsWith(valueTrim, TChar.CastFrom('-'))) { ReadOnlySpan<TChar> afterHyphen = valueTrim.Slice(1); elementsConsumed = value.Length - afterHyphen.Length; if (TryMatchSpecialValueSymbol(afterHyphen, nanSymbol, allowTrailingInvalid, ref elementsConsumed)) { result = TDecimal.Construct(TDecimal.NaN); return ParsingStatus.OK; } } } result = TDecimal.Construct(TDecimal.Zero); elementsConsumed = 0; return ParsingStatus.Failed; } result = NumberToDecimalIeee754<TDecimal, TValue>(ref number); return ParsingStatus.OK; } internal static bool SpanStartsWith<TChar>(ReadOnlySpan<TChar> span, TChar c) where TChar : unmanaged, IUtfChar<TChar> { return !span.IsEmpty && (span[0] == c); } internal static bool SpanStartsWith<TChar>(ReadOnlySpan<TChar> span, ReadOnlySpan<TChar> value, StringComparison comparisonType) where TChar : unmanaged, IUtfChar<TChar> { if (typeof(TChar) == typeof(char)) { ReadOnlySpan<char> typedSpan = Unsafe.BitCast<ReadOnlySpan<TChar>, ReadOnlySpan<char>>(span); ReadOnlySpan<char> typedValue = Unsafe.BitCast<ReadOnlySpan<TChar>, ReadOnlySpan<char>>(value); return typedSpan.StartsWith(typedValue, comparisonType); } else { Debug.Assert(typeof(TChar) == typeof(byte)); ReadOnlySpan<byte> typedSpan = Unsafe.BitCast<ReadOnlySpan<TChar>, ReadOnlySpan<byte>>(span); ReadOnlySpan<byte> typedValue = Unsafe.BitCast<ReadOnlySpan<TChar>, ReadOnlySpan<byte>>(value); return typedSpan.StartsWithUtf8(typedValue, comparisonType); } } internal static bool SpanEqualsOrdinalIgnoreCase<TChar>(ReadOnlySpan<TChar> span, ReadOnlySpan<TChar> value) where TChar : unmanaged, IUtfChar<TChar> { if (typeof(TChar) == typeof(char)) { ReadOnlySpan<char> typedSpan = Unsafe.BitCast<ReadOnlySpan<TChar>, ReadOnlySpan<char>>(span); ReadOnlySpan<char> typedValue = Unsafe.BitCast<ReadOnlySpan<TChar>, ReadOnlySpan<char>>(value); return typedSpan.EqualsOrdinalIgnoreCase(typedValue); } else { Debug.Assert(typeof(TChar) == typeof(byte)); ReadOnlySpan<byte> typedSpan = Unsafe.BitCast<ReadOnlySpan<TChar>, ReadOnlySpan<byte>>(span); ReadOnlySpan<byte> typedValue = Unsafe.BitCast<ReadOnlySpan<TChar>, ReadOnlySpan<byte>>(value); return typedSpan.EqualsOrdinalIgnoreCaseUtf8(typedValue); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ReadOnlySpan<TChar> SpanTrim<TChar>(ReadOnlySpan<TChar> span) where TChar : unmanaged, IUtfChar<TChar> { if (typeof(TChar) == typeof(char)) { return Unsafe.BitCast<ReadOnlySpan<char>, ReadOnlySpan<TChar>>(Unsafe.BitCast<ReadOnlySpan<TChar>, ReadOnlySpan<char>>(span).Trim()); } else { Debug.Assert(typeof(TChar) == typeof(byte)); return Unsafe.BitCast<ReadOnlySpan<byte>, ReadOnlySpan<TChar>>(Unsafe.BitCast<ReadOnlySpan<TChar>, ReadOnlySpan<byte>>(span).TrimUtf8()); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ReadOnlySpan<TChar> SpanTrimStart<TChar>(ReadOnlySpan<TChar> span) where TChar : unmanaged, IUtfChar<TChar> { if (typeof(TChar) == typeof(char)) { return Unsafe.BitCast<ReadOnlySpan<char>, ReadOnlySpan<TChar>>(Unsafe.BitCast<ReadOnlySpan<TChar>, ReadOnlySpan<char>>(span).TrimStart()); } else { Debug.Assert(typeof(TChar) == typeof(byte)); return Unsafe.BitCast<ReadOnlySpan<byte>, ReadOnlySpan<TChar>>(Unsafe.BitCast<ReadOnlySpan<TChar>, ReadOnlySpan<byte>>(span).TrimStartUtf8()); } } // Matches a special-value symbol (Infinity/NaN and signed variants) as a prefix of candidate, // which must be a suffix of value (i.e. value with any leading whitespace and sign removed). // elementsConsumed must be set by the caller to the offset of candidate within value; on success // it is advanced by the number of elements consumed here (the symbol plus any trailing whitespace), // and on failure it is left unchanged. Leading and trailing whitespace around a special value is // always consumed, independent of the AllowLeadingWhite/AllowTrailingWhite styles (historical // precedent). When a non-whitespace character remains after the trailing whitespace, the match is // only accepted when AllowTrailingInvalidCharacters is set, in which case parsing stops at that // character. internal static bool TryMatchSpecialValueSymbol<TChar>(ReadOnlySpan<TChar> candidate, ReadOnlySpan<TChar> symbol, bool allowTrailingInvalid, ref int elementsConsumed) where TChar : unmanaged, IUtfChar<TChar> { if (!symbol.IsEmpty && symbol.Length <= candidate.Length && SpanEqualsOrdinalIgnoreCase(candidate.Slice(0, symbol.Length), symbol)) { ReadOnlySpan<TChar> trailing = SpanTrimStart(candidate.Slice(symbol.Length)); if (trailing.IsEmpty || allowTrailingInvalid) { elementsConsumed += candidate.Length - trailing.Length; return true; } } return false; } internal static bool TryParseHexFloatingPoint<TChar, TFloat>(ReadOnlySpan<TChar> value, NumberStyles styles, NumberFormatInfo info, out TFloat result, out int elementsConsumed) where TChar : unmanaged, IUtfChar<TChar> where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo<TFloat> { result = TFloat.Zero; if (value.IsEmpty) { elementsConsumed = 0; return false; } int index = 0; // Skip leading whitespace if ((styles & NumberStyles.AllowLeadingWhite) != 0) { while (index < value.Length && IsWhite(TChar.CastToUInt32(value[index]))) { index++; } } if (index >= value.Length) { elementsConsumed = 0; return false; } // Parse optional sign bool isNegative = false; if ((styles & NumberStyles.AllowLeadingSign) != 0) { ReadOnlySpan<TChar> negativeSign = info.NegativeSignTChar<TChar>(); if (!negativeSign.IsEmpty && value.Slice(index).StartsWith(negativeSign)) { isNegative = true; index += negativeSign.Length; } else if (info.AllowHyphenDuringParsing() && TChar.CastToUInt32(value[index]) == '-') { isNegative = true; index++; } else { ReadOnlySpan<TChar> positiveSign = info.PositiveSignTChar<TChar>(); if (!positiveSign.IsEmpty && value.Slice(index).StartsWith(positiveSign)) { index += positiveSign.Length; } } } if (index >= value.Length) { elementsConsumed = 0; return false; } // Require "0x" or "0X" prefix (consistent with IEEE 754 conventions) if (TChar.CastToUInt32(value[index]) != '0' || index + 1 >= value.Length || (TChar.CastToUInt32(value[index + 1]) | 0x20) != 'x') { elementsConsumed = 0; return false; } index += 2; if (index >= value.Length) { elementsConsumed = 0; return false; } // Parse hex significand. // We accumulate up to 16 significant hex digits into a ulong. // We track the exponent adjustment due to digit position. // // The value is: significand * 2^(binaryExponent - 4 * fractionalDigitsConsumed + 4 * overflowIntegerDigits) ulong significand = 0; int significandDigits = 0; // Count of significant (non-leading-zero) digits consumed into significand int overflowIntegerDigits = 0; // Integer digits that didn't fit bool hasDiscardedNonZeroDigits = false; // IEEE 754 "sticky bit": any nonzero digit discarded beyond significand capacity int integerPartStart = index; while (index < value.Length) { uint ch = TChar.CastToUInt32(value[index]); int digit = HexConverter.FromChar((int)ch); if (digit >= 16) { break; } // Accumulate up to 16 significant hex digits. The '|| significand == 0' is // a defensive check: significandDigits only increments when a nonzero digit is // accumulated, so significandDigits >= 16 implies significand != 0 in practice. if (significandDigits < 16 || significand == 0) { if (significand != 0 || digit != 0) { significand = (significand << 4) | (uint)digit; significandDigits++; } } else { overflowIntegerDigits++; hasDiscardedNonZeroDigits |= digit != 0; } index++; } bool hasIntegerPart = index > integerPartStart; // Parse fractional part int fractionalDigitsConsumed = 0; bool hasFractionalPart = false; if ((styles & NumberStyles.AllowDecimalPoint) != 0 && index < value.Length) { ReadOnlySpan<TChar> decimalSeparator = info.NumberDecimalSeparatorTChar<TChar>(); if (value.Slice(index).StartsWith(decimalSeparator)) { index += decimalSeparator.Length; int fractionalPartStart = index; while (index < value.Length) { uint ch = TChar.CastToUInt32(value[index]); int digit = HexConverter.FromChar((int)ch); if (digit >= 16) { break; } // Accumulate significant digits (see integer loop comment for '|| significand == 0'). // Discarded fractional digits intentionally do NOT increment fractionalDigitsConsumed: // they are beyond significand precision and only contribute sticky bits for rounding. if (significandDigits < 16 || significand == 0) { if (significand != 0 || digit != 0) { significand = (significand << 4) | (uint)digit; significandDigits++; } // Always increment, even for leading zeros: positional value matters // (e.g., 0x0.004p0 = 4 * 2^-12, so all three fractional digits count). fractionalDigitsConsumed++; } else { hasDiscardedNonZeroDigits |= digit != 0; } index++; } hasFractionalPart = index > fractionalPartStart; } } if (!hasIntegerPart && !hasFractionalPart) { elementsConsumed = 0; return false; } // Parse the exponent: 'p' or 'P' followed by optional sign and decimal digits. // The decimal value specifies an exponent in the radix of the floating-point format // (for binary types, the value is multiplied by 2 raised to this power). int binaryExponent = 0; if (index < value.Length && ((TChar.CastToUInt32(value[index]) | 0x20) == 'p')) { index++; if (index >= value.Length) { elementsConsumed = 0; return false; } bool exponentIsNegative = false; ReadOnlySpan<TChar> negSign = info.NegativeSignTChar<TChar>(); ReadOnlySpan<TChar> posSign = info.PositiveSignTChar<TChar>(); if (!negSign.IsEmpty && value.Slice(index).StartsWith(negSign)) { exponentIsNegative = true; index += negSign.Length; } else if (info.AllowHyphenDuringParsing() && TChar.CastToUInt32(value[index]) == '-') { exponentIsNegative = true; index++; } else if (!posSign.IsEmpty && value.Slice(index).StartsWith(posSign)) { index += posSign.Length; } if (index >= value.Length) { elementsConsumed = 0; return false; } int exponentStart = index; while (index < value.Length) { uint ech = TChar.CastToUInt32(value[index]); if (!IsDigit(ech)) { break; } int digit = (int)(ech - '0'); // Saturate at int.MaxValue on overflow. Unlike the significand (which tracks // overflow digits and sticky bits for rounding), the exponent just needs to be // large enough to guarantee the result resolves to infinity or zero. binaryExponent = binaryExponent <= (int.MaxValue - digit) / 10 ? binaryExponent * 10 + digit : int.MaxValue; index++; } if (index == exponentStart) { elementsConsumed = 0; return false; } if (exponentIsNegative) { binaryExponent = -binaryExponent; } } else { // Exponent indicator (p/P) is required elementsConsumed = 0; return false; } // Skip trailing whitespace if ((styles & NumberStyles.AllowTrailingWhite) != 0) { while (index < value.Length && IsWhite(TChar.CastToUInt32(value[index]))) { index++; } } // For compatibility we still need to process any trailing // nulls that exist and report them as having been consumed. index = ConsumeTrailingNulls(value, index); if ((index != value.Length) && ((styles & AllowTrailingInvalidCharacters) == 0)) { elementsConsumed = 0; return false; } // We've successfully parsed a number, so now we just need to handle constructing the result elementsConsumed = index; if (significand == 0) { result = isNegative ? TFloat.NegativeZero : TFloat.Zero; return true; } // Compute the effective binary exponent. // value = significand * 2^(-4 * fractionalDigitsConsumed) * 2^(4 * overflowIntegerDigits) * 2^binaryExponent long exp = (long)binaryExponent - 4L * fractionalDigitsConsumed + 4L * overflowIntegerDigits; // Normalize: shift significand so MSB is at bit 63 int lz = BitOperations.LeadingZeroCount(significand); significand <<= lz; exp -= lz; // significand is now in [2^63, 2^64), so value = significand * 2^exp // = (significand / 2^63) * 2^(exp + 63) = 1.xxx * 2^(exp + 63) long actualExp = exp + 63; int mantissaBits = TFloat.DenormalMantissaBits; if (actualExp > TFloat.MaxBinaryExponent) { result = isNegative ? TFloat.NegativeInfinity : TFloat.PositiveInfinity; return true; } int shiftRight = 63 - mantissaBits; Debug.Assert(shiftRight >= 11, "shiftRight is always >= 11 for all IEEE float types (double: 11, float: 40, Half: 53, BFloat16: 56)"); long biasedExp = actualExp + TFloat.ExponentBias; if (biasedExp <= 0) { long denormalShift = 1L - biasedExp; if (denormalShift > 64 - shiftRight) { // Value is too small to round to min subnormal result = isNegative ? TFloat.NegativeZero : TFloat.Zero; return true; } shiftRight += (int)denormalShift; biasedExp = 0; } // Round to nearest, ties to even ulong mantissa = 0; if (shiftRight > 0 && shiftRight < 64) { ulong roundBit = 1UL << (shiftRight - 1); ulong stickyBits = (significand & (roundBit - 1)) | (hasDiscardedNonZeroDigits ? 1UL : 0UL); mantissa = significand >> shiftRight; if ((significand & roundBit) != 0 && (stickyBits != 0 || (mantissa & 1) != 0)) { mantissa++; if (biasedExp == 0 && mantissa > TFloat.DenormalMantissaMask) { biasedExp = 1; mantissa &= TFloat.DenormalMantissaMask; } else if (mantissa > ((1UL << (mantissaBits + 1)) - 1)) { mantissa >>= 1; biasedExp++; if (biasedExp >= TFloat.InfinityExponent) { result = isNegative ? TFloat.NegativeInfinity : TFloat.PositiveInfinity; return true; } } } } else if (shiftRight == 64) { // Significand is at bit 63. Round bit is bit 63, sticky bits are 62..0. ulong roundBit = 1UL << 63; ulong stickyBits = (significand & (roundBit - 1)) | (hasDiscardedNonZeroDigits ? 1UL : 0UL); mantissa = 0; // mantissa is 0 (even), so ties-to-even rounds up only when sticky bits are nonzero. if ((significand & roundBit) != 0 && stickyBits != 0) { mantissa = 1; if (mantissa > TFloat.DenormalMantissaMask) { biasedExp = 1; mantissa &= TFloat.DenormalMantissaMask; } } } // shiftRight > 64 is impossible: max is 63 - 7 + denormalShift, capped by the // early return when denormalShift > 64 - shiftRight. // shiftRight == 0 is impossible: minimum is 63 - 52 = 11 (for double), see assert above. Debug.Assert(shiftRight > 0 && shiftRight <= 64); mantissa &= TFloat.DenormalMantissaMask; ulong bits = ((ulong)biasedExp << mantissaBits) | mantissa; result = TFloat.BitsToFloat(bits); if (isNegative) { result = -result; } return true; } internal static unsafe bool TryParseFloat<TChar, TFloat>(ReadOnlySpan<TChar> value, NumberStyles styles, NumberFormatInfo info, out TFloat result, out int elementsConsumed) where TChar : unmanaged, IUtfChar<TChar> where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo<TFloat> { if ((styles & NumberStyles.AllowHexSpecifier) != 0) { if (TryParseHexFloatingPoint(value, styles, info, out result, out elementsConsumed)) { return true; } } else { NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, stackalloc byte[TFloat.NumberBufferLength]); if (TryStringToNumber(value, styles, ref number, info, out elementsConsumed)) { result = NumberToFloat<TFloat>(ref number); return true; } } // Both the hex and non-hex floating-point paths support the Infinity/NaN symbols, so // either falls through to the special-value handling below when the numeric parse fails. // // Leading and trailing whitespace around a special value (Infinity/NaN) is always // consumed, independent of the AllowLeadingWhite/AllowTrailingWhite styles (historical // precedent). When AllowTrailingInvalidCharacters is set, parsing additionally stops at // the first non-whitespace character after the symbol; otherwise such a trailing // character rejects the match. ReadOnlySpan<TChar> valueTrim = SpanTrimStart(value); bool allowTrailingInvalid = (styles & AllowTrailingInvalidCharacters) != 0; // elementsConsumed is seeded with the offset of the candidate within value (the leading // whitespace, plus any sign) and then advanced by TryMatchSpecialValueSymbol on a match. elementsConsumed = value.Length - valueTrim.Length; // This code would be simpler if we only had the concept of `InfinitySymbol`, but // we don't so we'll check the existing cases first and then handle `PositiveSign` + // `PositiveInfinitySymbol` and `PositiveSign/NegativeSign` + `NaNSymbol` last. ReadOnlySpan<TChar> positiveInfinitySymbol = info.PositiveInfinitySymbolTChar<TChar>(); if (TryMatchSpecialValueSymbol(valueTrim, positiveInfinitySymbol, allowTrailingInvalid, ref elementsConsumed)) { result = TFloat.PositiveInfinity; return true; } if (TryMatchSpecialValueSymbol(valueTrim, info.NegativeInfinitySymbolTChar<TChar>(), allowTrailingInvalid, ref elementsConsumed)) { result = TFloat.NegativeInfinity; return true; } ReadOnlySpan<TChar> nanSymbol = info.NaNSymbolTChar<TChar>(); if (TryMatchSpecialValueSymbol(valueTrim, nanSymbol, allowTrailingInvalid, ref elementsConsumed)) { result = TFloat.NaN; return true; } ReadOnlySpan<TChar> positiveSign = info.PositiveSignTChar<TChar>(); if (SpanStartsWith(valueTrim, positiveSign, StringComparison.OrdinalIgnoreCase)) { ReadOnlySpan<TChar> afterSign = valueTrim.Slice(positiveSign.Length); elementsConsumed = value.Length - afterSign.Length; if (TryMatchSpecialValueSymbol(afterSign, positiveInfinitySymbol, allowTrailingInvalid, ref elementsConsumed)) { result = TFloat.PositiveInfinity; return true; } else if (TryMatchSpecialValueSymbol(afterSign, nanSymbol, allowTrailingInvalid, ref elementsConsumed)) { result = TFloat.NaN; return true; } result = TFloat.Zero; elementsConsumed = 0; return false; } ReadOnlySpan<TChar> negativeSign = info.NegativeSignTChar<TChar>(); if (SpanStartsWith(valueTrim, negativeSign, StringComparison.OrdinalIgnoreCase)) { ReadOnlySpan<TChar> afterSign = valueTrim.Slice(negativeSign.Length); elementsConsumed = value.Length - afterSign.Length; if (TryMatchSpecialValueSymbol(afterSign, nanSymbol, allowTrailingInvalid, ref elementsConsumed)) { result = TFloat.NaN; return true; } if (info.AllowHyphenDuringParsing() && SpanStartsWith(valueTrim, TChar.CastFrom('-'))) { ReadOnlySpan<TChar> afterHyphen = valueTrim.Slice(1); elementsConsumed = value.Length - afterHyphen.Length; if (TryMatchSpecialValueSymbol(afterHyphen, nanSymbol, allowTrailingInvalid, ref elementsConsumed)) { result = TFloat.NaN; return true; } } } result = TFloat.Zero; elementsConsumed = 0; return false; // We really failed } [DoesNotReturn] internal static void ThrowOverflowOrFormatException<TChar, TInteger>(ParsingStatus status, ReadOnlySpan<TChar> value) where TChar : unmanaged, IUtfChar<TChar> where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo<TInteger> { if (status == ParsingStatus.Failed) { ThrowFormatException(value); } ThrowOverflowException<TInteger>(); } [DoesNotReturn] internal static void ThrowFormatException<TChar>(ReadOnlySpan<TChar> value) where TChar : unmanaged, IUtfChar<TChar> { string errorMessage; if (typeof(TChar) == typeof(byte)) { // Decode the UTF8 value into a string we can include in the error message. We're here // because we failed to parse, which also means the bytes might not be valid UTF8, // so fallback to a message that doesn't include the value if the bytes are invalid. // It's possible after we check the bytes for validity that they could be concurrently // mutated, but if that's happening, all bets are off, anyway, and it simply impacts // which exception is thrown. ReadOnlySpan<byte> bytes = Unsafe.BitCast<ReadOnlySpan<TChar>, ReadOnlySpan<byte>>(value); errorMessage = Utf8.IsValid(bytes) ? SR.Format(SR.Format_InvalidStringWithValue, Encoding.UTF8.GetString(bytes)) : SR.Format_InvalidString; } else { errorMessage = SR.Format(SR.Format_InvalidStringWithValue, value.ToString()); } throw new FormatException(errorMessage); } [DoesNotReturn] internal static void ThrowOverflowException<TInteger>() where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo<TInteger> { throw new OverflowException(TInteger.OverflowMessage); } [DoesNotReturn] internal static void ThrowDecimalOverflowException() { throw new OverflowException(SR.Overflow_Decimal); } internal static TFloat NumberToFloat<TFloat>(ref NumberBuffer number) where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo<TFloat> { number.CheckConsistency(); TFloat result; if ((number.DigitsCount == 0) || (number.Scale < TFloat.MinDecimalExponent)) { result = TFloat.Zero; } else if (number.Scale > TFloat.MaxDecimalExponent) { result = TFloat.PositiveInfinity; } else { ulong bits = NumberToFloatingPointBits<TFloat>(ref number); result = TFloat.BitsToFloat(bits); } return number.IsNegative ? -result : result; } internal static TDecimal NumberToDecimalIeee754<TDecimal, TValue>(ref NumberBuffer number) where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo<TDecimal, TValue> where TValue : unmanaged, IBinaryInteger<TValue> { number.CheckConsistency(); TValue value = NumberToDecimalIeee754Bits<TDecimal, TValue>(ref number); return TDecimal.Construct(value); } } }