From f66c1c15409ff505ba55f8fdf86bc5c041bf47a3 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 29 Nov 2023 18:04:03 +0800 Subject: [PATCH] Unify parsing part of BigInteger with CoreLib (#85978) --- .../src/System/Number.NumberBuffer.cs | 0 .../src/System/Number.Parsing.Common.cs | 378 ++++++++++++++++++ .../System.Private.CoreLib.Shared.projitems | 7 +- .../src/System/Number.Parsing.cs | 11 - .../src/System.Runtime.Numerics.csproj | 9 +- .../FormatProvider.BigInteger.cs | 39 -- .../Globalization/FormatProvider.Number.cs | 378 ------------------ .../BigNumber.cs => Number.BigInteger.cs} | 333 ++++++++------- .../src/System/Numerics/BigInteger.cs | 19 +- .../BigInteger/BigIntegerToStringTests.cs | 4 +- 10 files changed, 584 insertions(+), 594 deletions(-) rename src/libraries/{System.Private.CoreLib => Common}/src/System/Number.NumberBuffer.cs (100%) create mode 100644 src/libraries/Common/src/System/Number.Parsing.Common.cs rename src/libraries/{Common => System.Runtime.Numerics}/src/System/Globalization/FormatProvider.Number.cs (78%) rename src/libraries/System.Runtime.Numerics/src/System/{Numerics/BigNumber.cs => Number.BigInteger.cs} (86%) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.NumberBuffer.cs b/src/libraries/Common/src/System/Number.NumberBuffer.cs similarity index 100% rename from src/libraries/System.Private.CoreLib/src/System/Number.NumberBuffer.cs rename to src/libraries/Common/src/System/Number.NumberBuffer.cs diff --git a/src/libraries/Common/src/System/Number.Parsing.Common.cs b/src/libraries/Common/src/System/Number.Parsing.Common.cs new file mode 100644 index 0000000000000..8ebadf5a26725 --- /dev/null +++ b/src/libraries/Common/src/System/Number.Parsing.Common.cs @@ -0,0 +1,378 @@ +// 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.Globalization; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System +{ + internal static partial class Number + { + private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info) + { + Debug.Assert(str != null); + Debug.Assert(strEnd != null); + Debug.Assert(str <= strEnd); + Debug.Assert((styles & (NumberStyles.AllowHexSpecifier | NumberStyles.AllowBinarySpecifier)) == 0); + + const int StateSign = 0x0001; + const int StateParens = 0x0002; + const int StateDigits = 0x0004; + const int StateNonZero = 0x0008; + const int StateDecimal = 0x0010; + const int StateCurrency = 0x0020; + + Debug.Assert(number.DigitsCount == 0); + Debug.Assert(number.Scale == 0); + Debug.Assert(!number.IsNegative); + Debug.Assert(!number.HasNonZeroTail); + + number.CheckConsistency(); + + string decSep; // decimal separator from NumberFormatInfo. + string groupSep; // group separator from NumberFormatInfo. + string? currSymbol = null; // currency symbol from NumberFormatInfo. + + bool parsingCurrency = false; + if ((styles & NumberStyles.AllowCurrencySymbol) != 0) + { + currSymbol = info.CurrencySymbol; + + // The idea here is to match the currency separators and on failure match the number separators to keep the perf of VB's IsNumeric fast. + // The values of decSep are setup to use the correct relevant separator (currency in the if part and decimal in the else part). + decSep = info.CurrencyDecimalSeparator; + groupSep = info.CurrencyGroupSeparator; + parsingCurrency = true; + } + else + { + decSep = info.NumberDecimalSeparator; + groupSep = info.NumberGroupSeparator; + } + + int state = 0; + char* p = str; + char ch = p < strEnd ? *p : '\0'; + char* next; + + while (true) + { + // Eat whitespace unless we've found a sign which isn't followed by a currency symbol. + // "-Kr 1231.47" is legal but "- 1231.47" is not. + if (!IsWhite(ch) || (styles & NumberStyles.AllowLeadingWhite) == 0 || ((state & StateSign) != 0 && ((state & StateCurrency) == 0 && info.NumberNegativePattern != 2))) + { + if ((((styles & NumberStyles.AllowLeadingSign) != 0) && (state & StateSign) == 0) && ((next = MatchChars(p, strEnd, info.PositiveSign)) != null || ((next = MatchNegativeSignChars(p, strEnd, info)) != null && (number.IsNegative = true)))) + { + state |= StateSign; + p = next - 1; + } + else if (ch == '(' && ((styles & NumberStyles.AllowParentheses) != 0) && ((state & StateSign) == 0)) + { + state |= StateSign | StateParens; + number.IsNegative = true; + } + else if (currSymbol != null && (next = MatchChars(p, strEnd, currSymbol)) != null) + { + state |= StateCurrency; + currSymbol = null; + // We already found the currency symbol. There should not be more currency symbols. Set + // currSymbol to NULL so that we won't search it again in the later code path. + p = next - 1; + } + else + { + break; + } + } + ch = ++p < strEnd ? *p : '\0'; + } + + int digCount = 0; + int digEnd = 0; + int maxDigCount = number.Digits.Length - 1; + int numberOfTrailingZeros = 0; + + while (true) + { + if (IsDigit(ch)) + { + state |= StateDigits; + + if (ch != '0' || (state & StateNonZero) != 0) + { + if (digCount < maxDigCount) + { + number.Digits[digCount] = (byte)(ch); + if ((ch != '0') || (number.Kind != NumberBufferKind.Integer)) + { + digEnd = digCount + 1; + } + } + else if (ch != '0') + { + // For decimal and binary floating-point numbers, we only + // need to store digits up to maxDigCount. However, we still + // need to keep track of whether any additional digits past + // maxDigCount were non-zero, as that can impact rounding + // for an input that falls evenly between two representable + // results. + + number.HasNonZeroTail = true; + } + + if ((state & StateDecimal) == 0) + { + number.Scale++; + } + + if (digCount < maxDigCount) + { + // Handle a case like "53.0". We need to ignore trailing zeros in the fractional part for floating point numbers, so we keep a count of the number of trailing zeros and update digCount later + if (ch == '0') + { + numberOfTrailingZeros++; + } + else + { + numberOfTrailingZeros = 0; + } + } + digCount++; + state |= StateNonZero; + } + else if ((state & StateDecimal) != 0) + { + number.Scale--; + } + } + else if (((styles & NumberStyles.AllowDecimalPoint) != 0) && ((state & StateDecimal) == 0) && ((next = MatchChars(p, strEnd, decSep)) != null || (parsingCurrency && (state & StateCurrency) == 0) && (next = MatchChars(p, strEnd, info.NumberDecimalSeparator)) != null)) + { + state |= StateDecimal; + p = next - 1; + } + else if (((styles & NumberStyles.AllowThousands) != 0) && ((state & StateDigits) != 0) && ((state & StateDecimal) == 0) && ((next = MatchChars(p, strEnd, groupSep)) != null || (parsingCurrency && (state & StateCurrency) == 0) && (next = MatchChars(p, strEnd, info.NumberGroupSeparator)) != null)) + { + p = next - 1; + } + else + { + break; + } + ch = ++p < strEnd ? *p : '\0'; + } + + bool negExp = false; + number.DigitsCount = digEnd; + number.Digits[digEnd] = (byte)('\0'); + if ((state & StateDigits) != 0) + { + if ((ch == 'E' || ch == 'e') && ((styles & NumberStyles.AllowExponent) != 0)) + { + char* temp = p; + ch = ++p < strEnd ? *p : '\0'; + if ((next = MatchChars(p, strEnd, info.PositiveSign)) != null) + { + ch = (p = next) < strEnd ? *p : '\0'; + } + else if ((next = MatchNegativeSignChars(p, strEnd, info)) != null) + { + ch = (p = next) < strEnd ? *p : '\0'; + negExp = true; + } + if (IsDigit(ch)) + { + int exp = 0; + do + { + // Check if we are about to overflow past our limit of 9 digits + if (exp >= 100_000_000) + { + // Set exp to Int.MaxValue to signify the requested exponent is too large. This will lead to an OverflowException later. + exp = int.MaxValue; + number.Scale = 0; + + // Finish parsing the number, a FormatException could still occur later on. + while (char.IsAsciiDigit(ch)) + { + ch = ++p < strEnd ? *p : '\0'; + } + break; + } + + exp = exp * 10 + (ch - '0'); + ch = ++p < strEnd ? *p : '\0'; + } while (IsDigit(ch)); + if (negExp) + { + exp = -exp; + } + number.Scale += exp; + } + else + { + p = temp; + ch = p < strEnd ? *p : '\0'; + } + } + + if (number.Kind == NumberBufferKind.FloatingPoint && !number.HasNonZeroTail) + { + // Adjust the number buffer for trailing zeros + int numberOfFractionalDigits = digEnd - number.Scale; + if (numberOfFractionalDigits > 0) + { + numberOfTrailingZeros = Math.Min(numberOfTrailingZeros, numberOfFractionalDigits); + Debug.Assert(numberOfTrailingZeros >= 0); + number.DigitsCount = digEnd - numberOfTrailingZeros; + number.Digits[number.DigitsCount] = (byte)('\0'); + } + } + + while (true) + { + if (!IsWhite(ch) || (styles & NumberStyles.AllowTrailingWhite) == 0) + { + if ((styles & NumberStyles.AllowTrailingSign) != 0 && ((state & StateSign) == 0) && ((next = MatchChars(p, strEnd, info.PositiveSign)) != null || (((next = MatchNegativeSignChars(p, strEnd, info)) != null) && (number.IsNegative = true)))) + { + state |= StateSign; + p = next - 1; + } + else if (ch == ')' && ((state & StateParens) != 0)) + { + state &= ~StateParens; + } + else if (currSymbol != null && (next = MatchChars(p, strEnd, currSymbol)) != null) + { + currSymbol = null; + p = next - 1; + } + else + { + break; + } + } + ch = ++p < strEnd ? *p : '\0'; + } + if ((state & StateParens) == 0) + { + if ((state & StateNonZero) == 0) + { + if (number.Kind != NumberBufferKind.Decimal) + { + number.Scale = 0; + } + if ((number.Kind == NumberBufferKind.Integer) && (state & StateDecimal) == 0) + { + number.IsNegative = false; + } + } + str = p; + return true; + } + } + str = p; + return false; + } + + internal static unsafe bool TryStringToNumber(ReadOnlySpan value, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info) + { + Debug.Assert(info != null); + fixed (char* stringPointer = &MemoryMarshal.GetReference(value)) + { + char* p = stringPointer; + if (!TryParseNumber(ref p, p + value.Length, styles, ref number, info) + || ((int)(p - stringPointer) < value.Length && !TrailingZeros(value, (int)(p - stringPointer)))) + { + number.CheckConsistency(); + return false; + } + } + + number.CheckConsistency(); + return true; + } + + [MethodImpl(MethodImplOptions.NoInlining)] // rare slow path that shouldn't impact perf of the main use case + private static bool TrailingZeros(ReadOnlySpan value, int index) => + // For compatibility, we need to allow trailing zeros at the end of a number string + value.Slice(index).IndexOfAnyExcept('\0') < 0; + + private static bool IsWhite(uint ch) => (ch == 0x20) || ((ch - 0x09) <= (0x0D - 0x09)); + + private static bool IsDigit(uint ch) => (ch - '0') <= 9; + + internal enum ParsingStatus + { + OK, + Failed, + Overflow + } + + private static bool IsSpaceReplacingChar(char c) => c == '\u00a0' || c == '\u202f'; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe char* MatchNegativeSignChars(char* p, char* pEnd, NumberFormatInfo info) + { + char* ret = MatchChars(p, pEnd, info.NegativeSign); + if (ret == null && GetAllowHyphenDuringParsing(info) && p < pEnd && *p == '-') + { + ret = p + 1; + } + + return ret; + } + + private static unsafe char* MatchChars(char* p, char* pEnd, string value) + { + Debug.Assert(p != null && pEnd != null && p <= pEnd && value != null); + fixed (char* stringPointer = value) + { + char* str = stringPointer; + if (*str != '\0') + { + // We only hurt the failure case + // This fix is for French or Kazakh cultures. Since a user cannot type 0xA0 or 0x202F as a + // space character we use 0x20 space character instead to mean the same. + while (true) + { + char cp = p < pEnd ? *p : '\0'; + if (cp != *str && !(IsSpaceReplacingChar(*str) && cp == '\u0020')) + { + break; + } + p++; + str++; + if (*str == '\0') + return p; + } + } + } + + return null; + } + + // Helper for internal property +#if SYSTEM_PRIVATE_CORELIB + private static bool GetAllowHyphenDuringParsing(NumberFormatInfo info) => info.AllowHyphenDuringParsing; +#else + private static bool GetAllowHyphenDuringParsing(NumberFormatInfo info) + { + string negativeSign = info.NegativeSign; + return negativeSign.Length == 1 && + negativeSign[0] switch + { + '\u2012' or // Figure Dash + '\u207B' or // Superscript Minus + '\u208B' or // Subscript Minus + '\u2212' or // Minus Sign + '\u2796' or // Heavy Minus Sign + '\uFE63' or // Small Hyphen-Minus + '\uFF0D' => true, // Fullwidth Hyphen-Minus + _ => false + }; + } +#endif + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index c916e0ad2cc95..b9292676fba1a 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -580,7 +580,6 @@ - @@ -1420,6 +1419,12 @@ Common\System\NotImplemented.cs + + System\Number.NumberBuffer.cs + + + System\Number.Parsing.Common.cs + Common\System\Numerics\Crc32ReflectedTable.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs index 7716424359425..1ebe52162dbd0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -1361,17 +1361,6 @@ private static bool TrailingZeros(ReadOnlySpan value, int index) return null; } - private static bool IsWhite(uint ch) => (ch == 0x20) || ((ch - 0x09) <= (0x0D - 0x09)); - - private static bool IsDigit(uint ch) => (ch - '0') <= 9; - - internal enum ParsingStatus - { - OK, - Failed, - Overflow - } - [DoesNotReturn] internal static void ThrowOverflowOrFormatException(ParsingStatus status, ReadOnlySpan value) where TChar : unmanaged, IUtfChar diff --git a/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj b/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj index 127039b3c597e..b5142e8ca5377 100644 --- a/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj +++ b/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj @@ -17,21 +17,24 @@ - + + - + + diff --git a/src/libraries/System.Runtime.Numerics/src/System/Globalization/FormatProvider.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Globalization/FormatProvider.BigInteger.cs index 6651d7cce6454..a81f40c844cc8 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Globalization/FormatProvider.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Globalization/FormatProvider.BigInteger.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Security; using System.Text; namespace System.Globalization @@ -32,43 +31,5 @@ internal static void FormatBigInteger(ref ValueStringBuilder sb, int precision, } } } - - internal static bool TryStringToBigInteger( - ReadOnlySpan s, - NumberStyles styles, - NumberFormatInfo numberFormatInfo, - StringBuilder receiver, // Receives the decimal digits - out int precision, - out int scale, - out bool sign - ) - { - FormatProvider.Number.NumberBuffer numberBuffer = default; - - unsafe - { - // Note: because we passed a non-null StringBuilder (receiver) to TryStringToNumber, it streams the digits into - // that instead of numberBuffer.digits. This is quite important since numberBuffer.digits is a fixed-buffer size - // and BigNumbers can have an arbitrary number of digits. - // - // Just in case a bug is ever introduced into TryStringToNumber that violates this, set the pointer that numberBuffer.digits returns - // to something that will AV. - numberBuffer.overrideDigits = (char*)0x1; - } - if (!Number.TryStringToNumber(s, styles, ref numberBuffer, receiver, numberFormatInfo, parseDecimal: false)) - { - precision = default(int); - scale = default(int); - sign = default(bool); - return false; - } - else - { - precision = numberBuffer.precision; - scale = numberBuffer.scale; - sign = numberBuffer.sign; - return true; - } - } } } diff --git a/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs b/src/libraries/System.Runtime.Numerics/src/System/Globalization/FormatProvider.Number.cs similarity index 78% rename from src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs rename to src/libraries/System.Runtime.Numerics/src/System/Globalization/FormatProvider.Number.cs index b950fda8d7e27..49706430b574b 100644 --- a/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Globalization/FormatProvider.Number.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -283,385 +282,8 @@ internal partial class FormatProvider private static partial class Number { - // Constants used by number parsing - private const int NumberMaxDigits = 32; - internal const int DECIMAL_PRECISION = 29; // Decimal.DecCalc also uses this value - private static bool IsWhite(char ch) - { - return (((ch) == 0x20) || ((ch) >= 0x09 && (ch) <= 0x0D)); - } - - private static unsafe char* MatchChars(char* p, char* pEnd, string str) - { - fixed (char* stringPointer = str) - { - return MatchChars(p, pEnd, stringPointer); - } - } - - private static unsafe char* MatchChars(char* p, char* pEnd, char* str) - { - Debug.Assert(p != null && pEnd != null && p <= pEnd && str != null); - - if (*str == '\0') - { - return null; - } - - // We only hurt the failure case - // This fix is for French or Kazakh cultures. Since a user cannot type 0xA0 as a - // space character we use 0x20 space character instead to mean the same. - while (true) - { - char cp = p < pEnd ? *p : '\0'; - if (cp != *str && !(*str == '\u00a0' && cp == '\u0020')) - { - break; - } - p++; - str++; - if (*str == '\0') return p; - } - return null; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe bool AllowHyphenDuringParsing(NumberFormatInfo info) - { - string negativeSign = info.NegativeSign; - return negativeSign.Length == 1 && - negativeSign[0] switch { - '\u2012' or // Figure Dash - '\u207B' or // Superscript Minus - '\u208B' or // Subscript Minus - '\u2212' or // Minus Sign - '\u2796' or // Heavy Minus Sign - '\uFE63' or // Small Hyphen-Minus - '\uFF0D' => true, // Fullwidth Hyphen-Minus - _ => false - }; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe char* MatchNegativeSignChars(char* p, char* pEnd, string negativeSign, bool allowHyphenDuringParsing) - { - char *ret = MatchChars(p, pEnd, negativeSign); - if (ret == null && allowHyphenDuringParsing && p < pEnd && *p == '-') - { - ret = p + 1; - } - - return ret; - } - - private interface IDigitParser - { - static abstract bool IsValidChar(char c); - static abstract bool IsHexOrBinaryParser(); - } - - private readonly struct IntegerDigitParser : IDigitParser - { - public static bool IsValidChar(char c) => char.IsAsciiDigit(c); - - public static bool IsHexOrBinaryParser() => false; - } - - private readonly struct HexDigitParser : IDigitParser - { - public static bool IsValidChar(char c) => HexConverter.IsHexChar((int)c); - - public static bool IsHexOrBinaryParser() => true; - } - - private readonly struct BinaryDigitParser : IDigitParser - { - public static bool IsValidChar(char c) - { - return (uint)c - '0' <= 1; - } - - public static bool IsHexOrBinaryParser() => true; - } - - - private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles options, scoped ref NumberBuffer number, StringBuilder? sb, NumberFormatInfo numfmt, bool parseDecimal) - { - if ((options & NumberStyles.AllowHexSpecifier) != 0) - { - return ParseNumberStyle(ref str, strEnd, options, ref number, sb, numfmt, parseDecimal); - } - - if ((options & NumberStyles.AllowBinarySpecifier) != 0) - { - return ParseNumberStyle(ref str, strEnd, options, ref number, sb, numfmt, parseDecimal); - } - - return ParseNumberStyle(ref str, strEnd, options, ref number, sb, numfmt, parseDecimal); - } - - private static unsafe bool ParseNumberStyle(ref char* str, char* strEnd, NumberStyles options, scoped ref NumberBuffer number, StringBuilder? sb, NumberFormatInfo numfmt, bool parseDecimal) - where TDigitParser : struct, IDigitParser - { - Debug.Assert(str != null); - Debug.Assert(strEnd != null); - Debug.Assert(str <= strEnd); - - const int StateSign = 0x0001; - const int StateParens = 0x0002; - const int StateDigits = 0x0004; - const int StateNonZero = 0x0008; - const int StateDecimal = 0x0010; - const int StateCurrency = 0x0020; - - number.scale = 0; - number.sign = false; - string decSep; // Decimal separator from NumberFormatInfo. - string groupSep; // Group separator from NumberFormatInfo. - string? currSymbol = null; // Currency symbol from NumberFormatInfo. - - bool allowHyphenDuringParsing = AllowHyphenDuringParsing(numfmt); - - bool parsingCurrency = false; - if ((options & NumberStyles.AllowCurrencySymbol) != 0) - { - currSymbol = numfmt.CurrencySymbol; - // The idea here is to match the currency separators and on failure match the number separators to keep the perf of VB's IsNumeric fast. - // The values of decSep are setup to use the correct relevant separator (currency in the if part and decimal in the else part). - decSep = numfmt.CurrencyDecimalSeparator; - groupSep = numfmt.CurrencyGroupSeparator; - parsingCurrency = true; - } - else - { - decSep = numfmt.NumberDecimalSeparator; - groupSep = numfmt.NumberGroupSeparator; - } - - int state = 0; - bool bigNumber = (sb != null); // When a StringBuilder is provided then we use it in place of the number.digits char[50] - int maxParseDigits = bigNumber ? int.MaxValue : NumberMaxDigits; - - char* p = str; - char ch = p < strEnd ? *p : '\0'; - char* next; - - char* dig = number.digits; - - while (true) - { - // Eat whitespace unless we've found a sign which isn't followed by a currency symbol. - // "-Kr 1231.47" is legal but "- 1231.47" is not. - if (!IsWhite(ch) || (options & NumberStyles.AllowLeadingWhite) == 0 || ((state & StateSign) != 0 && ((state & StateCurrency) == 0 && numfmt.NumberNegativePattern != 2))) - { - if ((((options & NumberStyles.AllowLeadingSign) != 0) && (state & StateSign) == 0) && ((next = MatchChars(p, strEnd, numfmt.PositiveSign)) != null || ((next = MatchNegativeSignChars(p, strEnd, numfmt.NegativeSign, allowHyphenDuringParsing)) != null && (number.sign = true)))) - { - state |= StateSign; - p = next - 1; - } - else if (ch == '(' && ((options & NumberStyles.AllowParentheses) != 0) && ((state & StateSign) == 0)) - { - state |= StateSign | StateParens; - number.sign = true; - } - else if (currSymbol != null && (next = MatchChars(p, strEnd, currSymbol)) != null) - { - state |= StateCurrency; - currSymbol = null; - - // We already found the currency symbol. There should not be more currency symbols. Set - // currSymbol to NULL so that we won't search it again in the later code path. - p = next - 1; - } - else - { - break; - } - } - ch = ++p < strEnd ? *p : '\0'; - } - - int digCount = 0; - int digEnd = 0; - while (true) - { - if (TDigitParser.IsValidChar(ch)) - { - state |= StateDigits; - - if (ch != '0' || (state & StateNonZero) != 0 || (bigNumber && TDigitParser.IsHexOrBinaryParser())) - { - if (digCount < maxParseDigits) - { - if (bigNumber) - { - sb!.Append(ch); - } - else - { - dig[digCount++] = ch; - } - - if (ch != '0' || parseDecimal) - { - digEnd = digCount; - } - } - if ((state & StateDecimal) == 0) - { - number.scale++; - } - state |= StateNonZero; - } - else if ((state & StateDecimal) != 0) - { - number.scale--; - } - } - else if (((options & NumberStyles.AllowDecimalPoint) != 0) && ((state & StateDecimal) == 0) && ((next = MatchChars(p, strEnd, decSep)) != null || ((parsingCurrency) && (state & StateCurrency) == 0) && (next = MatchChars(p, strEnd, numfmt.NumberDecimalSeparator)) != null)) - { - state |= StateDecimal; - p = next - 1; - } - else if (((options & NumberStyles.AllowThousands) != 0) && ((state & StateDigits) != 0) && ((state & StateDecimal) == 0) && ((next = MatchChars(p, strEnd, groupSep)) != null || ((parsingCurrency) && (state & StateCurrency) == 0) && (next = MatchChars(p, strEnd, numfmt.NumberGroupSeparator)) != null)) - { - p = next - 1; - } - else - { - break; - } - ch = ++p < strEnd ? *p : '\0'; - } - - bool negExp = false; - number.precision = digEnd; - if (bigNumber) - sb!.Append('\0'); - else - dig[digEnd] = '\0'; - if ((state & StateDigits) != 0) - { - if ((ch == 'E' || ch == 'e') && ((options & NumberStyles.AllowExponent) != 0)) - { - char* temp = p; - ch = ++p < strEnd ? *p : '\0'; - if ((next = MatchChars(p, strEnd, numfmt.PositiveSign)) != null) - { - ch = (p = next) < strEnd ? *p : '\0'; - } - else if ((next = MatchNegativeSignChars(p, strEnd, numfmt.NegativeSign, allowHyphenDuringParsing)) != null) - { - ch = (p = next) < strEnd ? *p : '\0'; - negExp = true; - } - if (char.IsAsciiDigit(ch)) - { - int exp = 0; - do - { - // Check if we are about to overflow past our limit of 9 digits - if (exp >= 100_000_000) - { - // Set exp to Int.MaxValue to signify the requested exponent is too large. This will lead to an OverflowException later. - exp = int.MaxValue; - number.scale = 0; - - // Finish parsing the number, a FormatException could still occur later on. - while (char.IsAsciiDigit(ch)) - { - ch = ++p < strEnd ? *p : '\0'; - } - break; - } - - exp = exp * 10 + (ch - '0'); - ch = ++p < strEnd ? *p : '\0'; - - } while (char.IsAsciiDigit(ch)); - - if (negExp) - { - exp = -exp; - } - number.scale += exp; - } - else - { - p = temp; - ch = p < strEnd ? *p : '\0'; - } - } - while (true) - { - if (!IsWhite(ch) || (options & NumberStyles.AllowTrailingWhite) == 0) - { - if (((options & NumberStyles.AllowTrailingSign) != 0 && ((state & StateSign) == 0)) && ((next = MatchChars(p, strEnd, numfmt.PositiveSign)) != null || (((next = MatchNegativeSignChars(p, strEnd, numfmt.NegativeSign, allowHyphenDuringParsing)) != null) && (number.sign = true)))) - { - state |= StateSign; - p = next - 1; - } - else if (ch == ')' && ((state & StateParens) != 0)) - { - state &= ~StateParens; - } - else if (currSymbol != null && (next = MatchChars(p, strEnd, currSymbol)) != null) - { - currSymbol = null; - p = next - 1; - } - else - { - break; - } - } - ch = ++p < strEnd ? *p : '\0'; - } - if ((state & StateParens) == 0) - { - if ((state & StateNonZero) == 0) - { - if (!parseDecimal) - { - number.scale = 0; - } - if ((state & StateDecimal) == 0) - { - number.sign = false; - } - } - str = p; - return true; - } - } - str = p; - return false; - } - - [MethodImpl(MethodImplOptions.NoInlining)] // rare slow path that shouldn't impact perf of the main use case - private static bool TrailingZeros(ReadOnlySpan s, int index) => - // For compatibility, we need to allow trailing zeros at the end of a number string - !s.Slice(index).ContainsAnyExcept('\0'); - - internal static unsafe bool TryStringToNumber(ReadOnlySpan str, NumberStyles options, scoped ref NumberBuffer number, StringBuilder sb, NumberFormatInfo numfmt, bool parseDecimal) - { - Debug.Assert(numfmt != null); - - fixed (char* stringPointer = &MemoryMarshal.GetReference(str)) - { - char* p = stringPointer; - if (!ParseNumber(ref p, p + str.Length, options, ref number, sb, numfmt, parseDecimal) - || (p - stringPointer < str.Length && !TrailingZeros(str, (int)(p - stringPointer)))) - { - return false; - } - } - - return true; - } - // ********************************************************************************************************** // // The remaining code in this module is an almost direct translation from the original unmanaged version in diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs similarity index 86% rename from src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs rename to src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index bf17f17062ea6..62272c70bd014 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -274,12 +274,13 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Numerics; using System.Runtime.InteropServices; using System.Text; -namespace System.Numerics +namespace System { - internal static class BigNumber + internal static partial class Number { private const NumberStyles InvalidNumberStyles = ~(NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite | NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingSign @@ -290,13 +291,6 @@ internal static class BigNumber private static ReadOnlySpan UInt32PowersOfTen => [1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000]; - internal enum ParsingStatus - { - OK, - Failed, - Overflow - } - [DoesNotReturn] internal static void ThrowOverflowOrFormatException(ParsingStatus status) => throw GetException(status); @@ -307,22 +301,6 @@ private static Exception GetException(ParsingStatus status) : new OverflowException(SR.Overflow_ParseBigInteger); } - private struct BigNumberBuffer - { - public StringBuilder digits; - public int precision; - public int scale; - public bool sign; // negative sign exists - - public static BigNumberBuffer Create() - { - BigNumberBuffer number = default; - number.digits = new StringBuilder(); - return number; - } - } - - internal static bool TryValidateParseStyleInteger(NumberStyles style, [NotNullWhen(false)] out ArgumentException? e) { // Check for undefined flags @@ -343,49 +321,63 @@ internal static bool TryValidateParseStyleInteger(NumberStyles style, [NotNullWh return true; } - internal static ParsingStatus TryParseBigInteger(string? value, NumberStyles style, NumberFormatInfo info, out BigInteger result) - { - if (value == null) - { - result = default; - return ParsingStatus.Failed; - } - - return TryParseBigInteger(value.AsSpan(), style, info, out result); - } - - internal static ParsingStatus TryParseBigInteger(ReadOnlySpan value, NumberStyles style, NumberFormatInfo info, out BigInteger result) + internal static unsafe ParsingStatus TryParseBigInteger(ReadOnlySpan value, NumberStyles style, NumberFormatInfo info, out BigInteger result) { if (!TryValidateParseStyleInteger(style, out ArgumentException? e)) { throw e; // TryParse still throws ArgumentException on invalid NumberStyles } - BigNumberBuffer bigNumber = BigNumberBuffer.Create(); - if (!FormatProvider.TryStringToBigInteger(value, style, info, bigNumber.digits, out bigNumber.precision, out bigNumber.scale, out bigNumber.sign)) - { - result = default; - return ParsingStatus.Failed; - } - if ((style & NumberStyles.AllowHexSpecifier) != 0) { - return HexNumberToBigInteger(ref bigNumber, out result); + return TryParseBigIntegerHexNumberStyle(value, style, out result); } if ((style & NumberStyles.AllowBinarySpecifier) != 0) { - return BinaryNumberToBigInteger(ref bigNumber, out result); + return TryParseBigIntegerBinaryNumberStyle(value, style, out result); } - return NumberToBigInteger(ref bigNumber, out result); + return TryParseBigIntegerNumber(value, style, info, out result); } - internal static BigInteger ParseBigInteger(string value, NumberStyles style, NumberFormatInfo info) + internal static unsafe ParsingStatus TryParseBigIntegerNumber(ReadOnlySpan value, NumberStyles style, NumberFormatInfo info, out BigInteger result) { - ArgumentNullException.ThrowIfNull(value); + scoped Span buffer; + byte[]? arrayFromPool = null; + + if (value.Length < 255) + { + buffer = stackalloc byte[value.Length + 1 + 1]; + } + else + { + buffer = arrayFromPool = ArrayPool.Shared.Rent(value.Length + 1 + 1); + } - return ParseBigInteger(value.AsSpan(), style, info); + ParsingStatus ret; + + fixed (byte* ptr = buffer) // NumberBuffer expects pinned span + { + NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, buffer); + + if (!TryStringToNumber(value, style, ref number, info)) + { + result = default; + ret = ParsingStatus.Failed; + } + else + { + ret = NumberToBigInteger(ref number, out result); + } + } + + if (arrayFromPool != null) + { + ArrayPool.Shared.Return(arrayFromPool); + } + + return ret; } internal static BigInteger ParseBigInteger(ReadOnlySpan value, NumberStyles style, NumberFormatInfo info) @@ -404,17 +396,42 @@ internal static BigInteger ParseBigInteger(ReadOnlySpan value, NumberStyle return result; } - private static ParsingStatus HexNumberToBigInteger(ref BigNumberBuffer number, out BigInteger result) + internal static ParsingStatus TryParseBigIntegerHexNumberStyle(ReadOnlySpan value, NumberStyles style, out BigInteger result) { - if (number.digits == null || number.digits.Length == 0) + int whiteIndex = 0; + + // Skip past any whitespace at the beginning. + if ((style & NumberStyles.AllowLeadingWhite) != 0) { - result = default; - return ParsingStatus.Failed; + for (whiteIndex = 0; whiteIndex < value.Length; whiteIndex++) + { + if (!IsWhite(value[whiteIndex])) + break; + } + + value = value[whiteIndex..]; + } + + // Skip past any whitespace at the end. + if ((style & NumberStyles.AllowTrailingWhite) != 0) + { + for (whiteIndex = value.Length - 1; whiteIndex >= 0; whiteIndex--) + { + if (!IsWhite(value[whiteIndex])) + break; + } + + value = value[..(whiteIndex + 1)]; + } + + if (value.IsEmpty) + { + goto FailExit; } const int DigitsPerBlock = 8; - int totalDigitCount = number.digits.Length - 1; // Ignore trailing '\0' + int totalDigitCount = value.Length; int blockCount, partialDigitCount; blockCount = Math.DivRem(totalDigitCount, DigitsPerBlock, out int remainder); @@ -428,7 +445,8 @@ private static ParsingStatus HexNumberToBigInteger(ref BigNumberBuffer number, o partialDigitCount = DigitsPerBlock - remainder; } - bool isNegative = HexConverter.FromChar(number.digits[0]) >= 8; + if (!HexConverter.IsHexChar(value[0])) goto FailExit; + bool isNegative = HexConverter.FromChar(value[0]) >= 8; uint partialValue = (isNegative && partialDigitCount > 0) ? 0xFFFFFFFFu : 0; uint[]? arrayFromPool = null; @@ -441,30 +459,22 @@ private static ParsingStatus HexNumberToBigInteger(ref BigNumberBuffer number, o try { - foreach (ReadOnlyMemory digitsChunkMem in number.digits.GetChunks()) + for (int i = 0; i < value.Length; i++) { - ReadOnlySpan chunkDigits = digitsChunkMem.Span; - for (int i = 0; i < chunkDigits.Length; i++) - { - char digitChar = chunkDigits[i]; - if (digitChar == '\0') - { - break; - } + char digitChar = value[i]; - int hexValue = HexConverter.FromChar(digitChar); - Debug.Assert(hexValue != 0xFF); + if (!HexConverter.IsHexChar(digitChar)) goto FailExit; + int hexValue = HexConverter.FromChar(digitChar); - partialValue = (partialValue << 4) | (uint)hexValue; - partialDigitCount++; + partialValue = (partialValue << 4) | (uint)hexValue; + partialDigitCount++; - if (partialDigitCount == DigitsPerBlock) - { - bitsBuffer[bitsBufferPos] = partialValue; - bitsBufferPos--; - partialValue = 0; - partialDigitCount = 0; - } + if (partialDigitCount == DigitsPerBlock) + { + bitsBuffer[bitsBufferPos] = partialValue; + bitsBufferPos--; + partialValue = 0; + partialDigitCount = 0; } } @@ -507,17 +517,46 @@ private static ParsingStatus HexNumberToBigInteger(ref BigNumberBuffer number, o ArrayPool.Shared.Return(arrayFromPool); } } + + FailExit: + result = default; + return ParsingStatus.Failed; } - private static ParsingStatus BinaryNumberToBigInteger(ref BigNumberBuffer number, out BigInteger result) + internal static ParsingStatus TryParseBigIntegerBinaryNumberStyle(ReadOnlySpan value, NumberStyles style, out BigInteger result) { - if (number.digits is null || number.digits.Length == 0) + int whiteIndex = 0; + + // Skip past any whitespace at the beginning. + if ((style & NumberStyles.AllowLeadingWhite) != 0) { - result = default; - return ParsingStatus.Failed; + for (whiteIndex = 0; whiteIndex < value.Length; whiteIndex++) + { + if (!IsWhite(value[whiteIndex])) + break; + } + + value = value[whiteIndex..]; + } + + // Skip past any whitespace at the end. + if ((style & NumberStyles.AllowTrailingWhite) != 0) + { + for (whiteIndex = value.Length - 1; whiteIndex >= 0; whiteIndex--) + { + if (!IsWhite(value[whiteIndex])) + break; + } + + value = value[..(whiteIndex + 1)]; } - int totalDigitCount = number.digits.Length - 1; // Ignore trailing '\0' + if (value.IsEmpty) + { + goto FailExit; + } + + int totalDigitCount = value.Length; int partialDigitCount; (int blockCount, int remainder) = int.DivRem(totalDigitCount, BigInteger.kcbitUint); @@ -531,8 +570,8 @@ private static ParsingStatus BinaryNumberToBigInteger(ref BigNumberBuffer number partialDigitCount = BigInteger.kcbitUint - remainder; } - Debug.Assert(number.digits[0] is '0' or '1'); - bool isNegative = number.digits[0] == '1'; + if (value[0] is not ('0' or '1')) goto FailExit; + bool isNegative = value[0] == '1'; uint currentBlock = isNegative ? 0xFF_FF_FF_FFu : 0x0; uint[]? arrayFromPool = null; @@ -544,28 +583,20 @@ private static ParsingStatus BinaryNumberToBigInteger(ref BigNumberBuffer number try { - foreach (ReadOnlyMemory digitsChunkMem in number.digits.GetChunks()) + for (int i = 0; i < value.Length; i++) { - ReadOnlySpan chunkDigits = digitsChunkMem.Span; - for (int i = 0; i < chunkDigits.Length; i++) - { - char digitChar = chunkDigits[i]; - if (digitChar == '\0') - { - break; - } + char digitChar = value[i]; - Debug.Assert(digitChar is '0' or '1'); - currentBlock = (currentBlock << 1) | (uint)(digitChar - '0'); - partialDigitCount++; + if (digitChar is not ('0' or '1')) goto FailExit; + currentBlock = (currentBlock << 1) | (uint)(digitChar - '0'); + partialDigitCount++; - if (partialDigitCount == BigInteger.kcbitUint) - { - buffer[bufferPos--] = currentBlock; - partialDigitCount = 0; + if (partialDigitCount == BigInteger.kcbitUint) + { + buffer[bufferPos--] = currentBlock; + partialDigitCount = 0; - // we do not need to reset currentBlock now, because it should always set all its bits by left shift in subsequent iterations - } + // we do not need to reset currentBlock now, because it should always set all its bits by left shift in subsequent iterations } } @@ -613,6 +644,10 @@ private static ParsingStatus BinaryNumberToBigInteger(ref BigNumberBuffer number ArrayPool.Shared.Return(arrayFromPool); } } + + FailExit: + result = default; + return ParsingStatus.Failed; } // @@ -623,12 +658,12 @@ private static ParsingStatus BinaryNumberToBigInteger(ref BigNumberBuffer number // a divide-and-conquer algorithm with a running time of O(NlogN). // private static int s_naiveThreshold = 20000; // non-readonly for testing - private static ParsingStatus NumberToBigInteger(ref BigNumberBuffer number, out BigInteger result) + private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out BigInteger result) { int currentBufferSize = 0; int totalDigitCount = 0; - int numberScale = number.scale; + int numberScale = number.Scale; const int MaxPartialDigits = 9; const uint TenPowMaxPartial = 1000000000; @@ -649,7 +684,7 @@ private static ParsingStatus NumberToBigInteger(ref BigNumberBuffer number, out try { - if (number.digits.Length <= s_naiveThreshold) + if (number.DigitsCount <= s_naiveThreshold) { return Naive(ref number, out result); } @@ -666,20 +701,17 @@ private static ParsingStatus NumberToBigInteger(ref BigNumberBuffer number, out } } - ParsingStatus Naive(ref BigNumberBuffer number, out BigInteger result) + ParsingStatus Naive(ref NumberBuffer number, out BigInteger result) { Span stackBuffer = stackalloc uint[BigIntegerCalculator.StackAllocThreshold]; Span currentBuffer = stackBuffer; uint partialValue = 0; int partialDigitCount = 0; - foreach (ReadOnlyMemory digitsChunk in number.digits.GetChunks()) + if (!ProcessChunk(number.Digits[..number.DigitsCount], ref currentBuffer)) { - if (!ProcessChunk(digitsChunk.Span, ref currentBuffer)) - { - result = default; - return ParsingStatus.Failed; - } + result = default; + return ParsingStatus.Failed; } if (partialDigitCount > 0) @@ -687,13 +719,13 @@ ParsingStatus Naive(ref BigNumberBuffer number, out BigInteger result) MultiplyAdd(ref currentBuffer, UInt32PowersOfTen[partialDigitCount], partialValue); } - result = NumberBufferToBigInteger(currentBuffer, number.sign); + result = NumberBufferToBigInteger(currentBuffer, number.IsNegative); return ParsingStatus.OK; - bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) + bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) { int remainingIntDigitCount = Math.Max(numberScale - totalDigitCount, 0); - ReadOnlySpan intDigitsSpan = chunkDigits.Slice(0, Math.Min(remainingIntDigitCount, chunkDigits.Length)); + ReadOnlySpan intDigitsSpan = chunkDigits.Slice(0, Math.Min(remainingIntDigitCount, chunkDigits.Length)); bool endReached = false; @@ -704,7 +736,7 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) for (int i = 0; i < intDigitsSpan.Length; i++) { - char digitChar = chunkDigits[i]; + char digitChar = (char)chunkDigits[i]; if (digitChar == '\0') { endReached = true; @@ -727,10 +759,10 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) // Check for nonzero digits after the decimal point. if (!endReached) { - ReadOnlySpan fracDigitsSpan = chunkDigits.Slice(intDigitsSpan.Length); + ReadOnlySpan fracDigitsSpan = chunkDigits.Slice(intDigitsSpan.Length); for (int i = 0; i < fracDigitsSpan.Length; i++) { - char digitChar = fracDigitsSpan[i]; + char digitChar = (char)fracDigitsSpan[i]; if (digitChar == '\0') { break; @@ -750,13 +782,13 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) } } - ParsingStatus DivideAndConquer(ref BigNumberBuffer number, out BigInteger result) + ParsingStatus DivideAndConquer(ref NumberBuffer number, out BigInteger result) { Span currentBuffer; int[]? arrayFromPoolForMultiplier = null; try { - totalDigitCount = Math.Min(number.digits.Length - 1, numberScale); + totalDigitCount = Math.Min(number.DigitsCount, numberScale); int bufferSize = (totalDigitCount + MaxPartialDigits - 1) / MaxPartialDigits; Span buffer = new uint[bufferSize]; @@ -771,44 +803,43 @@ ParsingStatus DivideAndConquer(ref BigNumberBuffer number, out BigInteger result uint currentBlock = 0; int shiftUntil = (totalDigitCount - 1) % MaxPartialDigits; int remainingIntDigitCount = totalDigitCount; - foreach (ReadOnlyMemory digitsChunk in number.digits.GetChunks()) - { - ReadOnlySpan digitsChunkSpan = digitsChunk.Span; - ReadOnlySpan intDigitsSpan = digitsChunkSpan.Slice(0, Math.Min(remainingIntDigitCount, digitsChunkSpan.Length)); - for (int i = 0; i < intDigitsSpan.Length; i++) + ReadOnlySpan digitsChunkSpan = number.Digits[..number.DigitsCount]; + ReadOnlySpan intDigitsSpan = digitsChunkSpan.Slice(0, Math.Min(remainingIntDigitCount, digitsChunkSpan.Length)); + + for (int i = 0; i < intDigitsSpan.Length; i++) + { + char digitChar = (char)intDigitsSpan[i]; + Debug.Assert(char.IsDigit(digitChar)); + currentBlock *= 10; + currentBlock += unchecked((uint)(digitChar - '0')); + if (shiftUntil == 0) { - char digitChar = intDigitsSpan[i]; - Debug.Assert(char.IsDigit(digitChar)); - currentBlock *= 10; - currentBlock += unchecked((uint)(digitChar - '0')); - if (shiftUntil == 0) - { - buffer[bufferIndex] = currentBlock; - currentBlock = 0; - bufferIndex--; - shiftUntil = MaxPartialDigits; - } - shiftUntil--; + buffer[bufferIndex] = currentBlock; + currentBlock = 0; + bufferIndex--; + shiftUntil = MaxPartialDigits; } - remainingIntDigitCount -= intDigitsSpan.Length; - Debug.Assert(0 <= remainingIntDigitCount); + shiftUntil--; + } + remainingIntDigitCount -= intDigitsSpan.Length; + Debug.Assert(0 <= remainingIntDigitCount); - ReadOnlySpan fracDigitsSpan = digitsChunkSpan.Slice(intDigitsSpan.Length); - for (int i = 0; i < fracDigitsSpan.Length; i++) + ReadOnlySpan fracDigitsSpan = digitsChunkSpan.Slice(intDigitsSpan.Length); + for (int i = 0; i < fracDigitsSpan.Length; i++) + { + char digitChar = (char)fracDigitsSpan[i]; + if (digitChar == '\0') { - char digitChar = fracDigitsSpan[i]; - if (digitChar == '\0') - { - break; - } - if (digitChar != '0') - { - result = default; - return ParsingStatus.Failed; - } + break; + } + if (digitChar != '0') + { + result = default; + return ParsingStatus.Failed; } } + Debug.Assert(currentBlock == 0); Debug.Assert(bufferIndex == -1); @@ -898,7 +929,7 @@ ParsingStatus DivideAndConquer(ref BigNumberBuffer number, out BigInteger result currentBufferSize--; } currentBuffer = buffer.Slice(0, currentBufferSize); - result = NumberBufferToBigInteger(currentBuffer, number.sign); + result = NumberBufferToBigInteger(currentBuffer, number.IsNegative); } finally { diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs index 6efa39c762962..ad88153227d40 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs @@ -669,7 +669,8 @@ public static BigInteger Parse(string value, IFormatProvider? provider) public static BigInteger Parse(string value, NumberStyles style, IFormatProvider? provider) { - return BigNumber.ParseBigInteger(value, style, NumberFormatInfo.GetInstance(provider)); + ArgumentNullException.ThrowIfNull(value); + return Parse(value.AsSpan(), style, NumberFormatInfo.GetInstance(provider)); } public static bool TryParse([NotNullWhen(true)] string? value, out BigInteger result) @@ -679,12 +680,12 @@ public static bool TryParse([NotNullWhen(true)] string? value, out BigInteger re public static bool TryParse([NotNullWhen(true)] string? value, NumberStyles style, IFormatProvider? provider, out BigInteger result) { - return BigNumber.TryParseBigInteger(value, style, NumberFormatInfo.GetInstance(provider), out result) == BigNumber.ParsingStatus.OK; + return TryParse(value.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result); } public static BigInteger Parse(ReadOnlySpan value, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) { - return BigNumber.ParseBigInteger(value, style, NumberFormatInfo.GetInstance(provider)); + return Number.ParseBigInteger(value, style, NumberFormatInfo.GetInstance(provider)); } public static bool TryParse(ReadOnlySpan value, out BigInteger result) @@ -694,7 +695,7 @@ public static bool TryParse(ReadOnlySpan value, out BigInteger result) public static bool TryParse(ReadOnlySpan value, NumberStyles style, IFormatProvider? provider, out BigInteger result) { - return BigNumber.TryParseBigInteger(value, style, NumberFormatInfo.GetInstance(provider), out result) == BigNumber.ParsingStatus.OK; + return Number.TryParseBigInteger(value, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; } public static int Compare(BigInteger left, BigInteger right) @@ -1556,22 +1557,22 @@ private int WriteTo(Span buffer) public override string ToString() { - return BigNumber.FormatBigInteger(this, null, NumberFormatInfo.CurrentInfo); + return Number.FormatBigInteger(this, null, NumberFormatInfo.CurrentInfo); } public string ToString(IFormatProvider? provider) { - return BigNumber.FormatBigInteger(this, null, NumberFormatInfo.GetInstance(provider)); + return Number.FormatBigInteger(this, null, NumberFormatInfo.GetInstance(provider)); } public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format) { - return BigNumber.FormatBigInteger(this, format, NumberFormatInfo.CurrentInfo); + return Number.FormatBigInteger(this, format, NumberFormatInfo.CurrentInfo); } public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, IFormatProvider? provider) { - return BigNumber.FormatBigInteger(this, format, NumberFormatInfo.GetInstance(provider)); + return Number.FormatBigInteger(this, format, NumberFormatInfo.GetInstance(provider)); } private string DebuggerDisplay @@ -1631,7 +1632,7 @@ private string DebuggerDisplay public bool TryFormat(Span destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan format = default, IFormatProvider? provider = null) { - return BigNumber.TryFormatBigInteger(this, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten); + return Number.TryFormatBigInteger(this, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten); } private static BigInteger Add(ReadOnlySpan leftBits, int leftSign, ReadOnlySpan rightBits, int rightSign) diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs index f052ae6f77929..f0219a252787a 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs @@ -513,7 +513,7 @@ public static void ToString_InvalidFormat_ThrowsFormatException() Assert.Throws(() => b.ToString("E1000000000")); Assert.Throws(() => b.ToString("E000001000000000")); - // Check ParseFormatSpecifier in BigNumber.cs with `G` format + // Check ParseFormatSpecifier in Number.BigInteger.cs with `G` format Assert.Throws(() => b.ToString("G" + int.MaxValue.ToString())); Assert.Throws(() => b.ToString("G" + intMaxPlus1String)); Assert.Throws(() => b.ToString("G4772185890")); @@ -534,7 +534,7 @@ public static void ToString_ValidLargeFormat() b.ToString("E999999999"); // Should not throw b.ToString("E00000999999999"); // Should not throw - // Check ParseFormatSpecifier in BigNumber.cs with `G` format + // Check ParseFormatSpecifier in Number.BigInteger.cs with `G` format b.ToString("G999999999"); // Should not throw b.ToString("G00000999999999"); // Should not throw }