File: src\runtime\src\libraries\System.Private.CoreLib\src\System\Number.Parsing.cs
Web Access
Project: src\runtime\src\coreclr\nativeaot\System.Private.CoreLib\src\System.Private.CoreLib.csproj (System.Private.CoreLib)
// 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);
        }
    }
}