diff --git a/src/libraries/Common/src/System/Number.Parsing.Common.cs b/src/libraries/Common/src/System/Number.Parsing.Common.cs index 8ebadf5a26725..16e9f777f3464 100644 --- a/src/libraries/Common/src/System/Number.Parsing.Common.cs +++ b/src/libraries/Common/src/System/Number.Parsing.Common.cs @@ -10,7 +10,8 @@ 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) + private static unsafe bool TryParseNumber(scoped ref TChar* str, TChar* strEnd, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info) + where TChar : unmanaged, IUtfChar { Debug.Assert(str != null); Debug.Assert(strEnd != null); @@ -31,39 +32,39 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu number.CheckConsistency(); - string decSep; // decimal separator from NumberFormatInfo. - string groupSep; // group separator from NumberFormatInfo. - string? currSymbol = null; // currency symbol from NumberFormatInfo. + ReadOnlySpan decSep; // decimal separator from NumberFormatInfo. + ReadOnlySpan groupSep; // group separator from NumberFormatInfo. + ReadOnlySpan currSymbol = ReadOnlySpan.Empty; // currency symbol from NumberFormatInfo. bool parsingCurrency = false; if ((styles & NumberStyles.AllowCurrencySymbol) != 0) { - currSymbol = info.CurrencySymbol; + currSymbol = info.CurrencySymbolTChar(); // 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; + decSep = info.CurrencyDecimalSeparatorTChar(); + groupSep = info.CurrencyGroupSeparatorTChar(); parsingCurrency = true; } else { - decSep = info.NumberDecimalSeparator; - groupSep = info.NumberGroupSeparator; + decSep = info.NumberDecimalSeparatorTChar(); + groupSep = info.NumberGroupSeparatorTChar(); } int state = 0; - char* p = str; - char ch = p < strEnd ? *p : '\0'; - char* next; + TChar* p = str; + uint ch = (p < strEnd) ? TChar.CastToUInt32(*p) : '\0'; + TChar* 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 (!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)))) + if (((styles & NumberStyles.AllowLeadingSign) != 0) && (state & StateSign) == 0 && ((next = MatchChars(p, strEnd, info.PositiveSignTChar())) != null || ((next = MatchNegativeSignChars(p, strEnd, info)) != null && (number.IsNegative = true)))) { state |= StateSign; p = next - 1; @@ -73,10 +74,10 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu state |= StateSign | StateParens; number.IsNegative = true; } - else if (currSymbol != null && (next = MatchChars(p, strEnd, currSymbol)) != null) + else if (!currSymbol.IsEmpty && (next = MatchChars(p, strEnd, currSymbol)) != null) { state |= StateCurrency; - currSymbol = null; + currSymbol = ReadOnlySpan.Empty; // 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; @@ -86,7 +87,7 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu break; } } - ch = ++p < strEnd ? *p : '\0'; + ch = ++p < strEnd ? TChar.CastToUInt32(*p) : '\0'; } int digCount = 0; @@ -104,7 +105,7 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu { if (digCount < maxDigCount) { - number.Digits[digCount] = (byte)(ch); + number.Digits[digCount] = (byte)ch; if ((ch != '0') || (number.Kind != NumberBufferKind.Integer)) { digEnd = digCount + 1; @@ -147,12 +148,12 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu 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)) + else if (((styles & NumberStyles.AllowDecimalPoint) != 0) && ((state & StateDecimal) == 0) && ((next = MatchChars(p, strEnd, decSep)) != null || (parsingCurrency && (state & StateCurrency) == 0 && (next = MatchChars(p, strEnd, info.NumberDecimalSeparatorTChar())) != 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)) + 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.NumberGroupSeparatorTChar())) != null))) { p = next - 1; } @@ -160,25 +161,25 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu { break; } - ch = ++p < strEnd ? *p : '\0'; + ch = ++p < strEnd ? TChar.CastToUInt32(*p) : '\0'; } bool negExp = false; number.DigitsCount = digEnd; - number.Digits[digEnd] = (byte)('\0'); + 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) + TChar* temp = p; + ch = ++p < strEnd ? TChar.CastToUInt32(*p) : '\0'; + if ((next = MatchChars(p, strEnd, info.PositiveSignTChar())) != null) { - ch = (p = next) < strEnd ? *p : '\0'; + ch = (p = next) < strEnd ? TChar.CastToUInt32(*p) : '\0'; } else if ((next = MatchNegativeSignChars(p, strEnd, info)) != null) { - ch = (p = next) < strEnd ? *p : '\0'; + ch = (p = next) < strEnd ? TChar.CastToUInt32(*p) : '\0'; negExp = true; } if (IsDigit(ch)) @@ -194,15 +195,15 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu number.Scale = 0; // Finish parsing the number, a FormatException could still occur later on. - while (char.IsAsciiDigit(ch)) + while (IsDigit(ch)) { - ch = ++p < strEnd ? *p : '\0'; + ch = ++p < strEnd ? TChar.CastToUInt32(*p) : '\0'; } break; } - exp = exp * 10 + (ch - '0'); - ch = ++p < strEnd ? *p : '\0'; + exp = (exp * 10) + (int)(ch - '0'); + ch = ++p < strEnd ? TChar.CastToUInt32(*p) : '\0'; } while (IsDigit(ch)); if (negExp) { @@ -213,7 +214,7 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu else { p = temp; - ch = p < strEnd ? *p : '\0'; + ch = p < strEnd ? TChar.CastToUInt32(*p) : '\0'; } } @@ -226,7 +227,7 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu numberOfTrailingZeros = Math.Min(numberOfTrailingZeros, numberOfFractionalDigits); Debug.Assert(numberOfTrailingZeros >= 0); number.DigitsCount = digEnd - numberOfTrailingZeros; - number.Digits[number.DigitsCount] = (byte)('\0'); + number.Digits[number.DigitsCount] = (byte)'\0'; } } @@ -234,7 +235,7 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu { 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)))) + if ((styles & NumberStyles.AllowTrailingSign) != 0 && ((state & StateSign) == 0) && ((next = MatchChars(p, strEnd, info.PositiveSignTChar())) != null || (((next = MatchNegativeSignChars(p, strEnd, info)) != null) && (number.IsNegative = true)))) { state |= StateSign; p = next - 1; @@ -243,9 +244,9 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu { state &= ~StateParens; } - else if (currSymbol != null && (next = MatchChars(p, strEnd, currSymbol)) != null) + else if (!currSymbol.IsEmpty && (next = MatchChars(p, strEnd, currSymbol)) != null) { - currSymbol = null; + currSymbol = ReadOnlySpan.Empty; p = next - 1; } else @@ -253,7 +254,7 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu break; } } - ch = ++p < strEnd ? *p : '\0'; + ch = ++p < strEnd ? TChar.CastToUInt32(*p) : '\0'; } if ((state & StateParens) == 0) { @@ -276,12 +277,15 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu return false; } - internal static unsafe bool TryStringToNumber(ReadOnlySpan value, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info) + internal static unsafe bool TryStringToNumber(ReadOnlySpan value, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info) + where TChar : unmanaged, IUtfChar { Debug.Assert(info != null); - fixed (char* stringPointer = &MemoryMarshal.GetReference(value)) + + fixed (TChar* stringPointer = &MemoryMarshal.GetReference(value)) { - char* p = stringPointer; + TChar* p = stringPointer; + if (!TryParseNumber(ref p, p + value.Length, styles, ref number, info) || ((int)(p - stringPointer) < value.Length && !TrailingZeros(value, (int)(p - stringPointer)))) { @@ -295,9 +299,12 @@ internal static unsafe bool TryStringToNumber(ReadOnlySpan value, NumberSt } [MethodImpl(MethodImplOptions.NoInlining)] // rare slow path that shouldn't impact perf of the main use case - private static bool TrailingZeros(ReadOnlySpan value, int index) => + private static bool TrailingZeros(ReadOnlySpan value, int index) + where TChar : unmanaged, IUtfChar + { // For compatibility, we need to allow trailing zeros at the end of a number string - value.Slice(index).IndexOfAnyExcept('\0') < 0; + return !value.Slice(index).ContainsAnyExcept(TChar.CastFrom('\0')); + } private static bool IsWhite(uint ch) => (ch == 0x20) || ((ch - 0x09) <= (0x0D - 0x09)); @@ -310,13 +317,15 @@ internal enum ParsingStatus Overflow } - private static bool IsSpaceReplacingChar(char c) => c == '\u00a0' || c == '\u202f'; + private static bool IsSpaceReplacingChar(uint c) => (c == '\u00a0') || (c == '\u202f'); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe char* MatchNegativeSignChars(char* p, char* pEnd, NumberFormatInfo info) + private static unsafe TChar* MatchNegativeSignChars(TChar* p, TChar* pEnd, NumberFormatInfo info) + where TChar : unmanaged, IUtfChar { - char* ret = MatchChars(p, pEnd, info.NegativeSign); - if (ret == null && GetAllowHyphenDuringParsing(info) && p < pEnd && *p == '-') + TChar* ret = MatchChars(p, pEnd, info.NegativeSignTChar()); + + if ((ret is null) && info.AllowHyphenDuringParsing() && (p < pEnd) && (TChar.CastToUInt32(*p) == '-')) { ret = p + 1; } @@ -324,55 +333,42 @@ internal enum ParsingStatus return ret; } - private static unsafe char* MatchChars(char* p, char* pEnd, string value) + private static unsafe TChar* MatchChars(TChar* p, TChar* pEnd, ReadOnlySpan value) + where TChar : unmanaged, IUtfChar { - Debug.Assert(p != null && pEnd != null && p <= pEnd && value != null); - fixed (char* stringPointer = value) + Debug.Assert((p != null) && (pEnd != null) && (p <= pEnd) && (value != null)); + + fixed (TChar* stringPointer = &MemoryMarshal.GetReference(value)) { - char* str = stringPointer; - if (*str != '\0') + TChar* str = stringPointer; + + if (TChar.CastToUInt32(*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')) + uint cp = (p < pEnd) ? TChar.CastToUInt32(*p) : '\0'; + uint val = TChar.CastToUInt32(*str); + + if ((cp != val) && !(IsSpaceReplacingChar(val) && (cp == '\u0020'))) { break; } + p++; str++; - if (*str == '\0') + + if (TChar.CastToUInt32(*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/Globalization/NumberFormatInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs index 7493c07b2a51f..a20ff0be7c6d1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs @@ -157,7 +157,7 @@ private static void VerifyDigitSubstitution(DigitShapes digitSub, string propert } internal bool HasInvariantNumberSigns => _hasInvariantNumberSigns; - internal bool AllowHyphenDuringParsing => _allowHyphenDuringParsing; + internal bool AllowHyphenDuringParsing() => _allowHyphenDuringParsing; private void InitializeInvariantAndNegativeSignFlags() { 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 72b423f265e78..952733c9268df 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -174,266 +174,6 @@ internal static TInteger ParseBinaryInteger(ReadOnlySpan return result; } - private static unsafe bool TryParseNumber(scoped ref TChar* str, TChar* strEnd, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info) - where TChar : unmanaged, IUtfChar - { - 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(); - - ReadOnlySpan decSep; // decimal separator from NumberFormatInfo. - ReadOnlySpan groupSep; // group separator from NumberFormatInfo. - ReadOnlySpan currSymbol = ReadOnlySpan.Empty; // currency symbol from NumberFormatInfo. - - bool parsingCurrency = false; - if ((styles & NumberStyles.AllowCurrencySymbol) != 0) - { - currSymbol = info.CurrencySymbolTChar(); - - // 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.CurrencyDecimalSeparatorTChar(); - groupSep = info.CurrencyGroupSeparatorTChar(); - parsingCurrency = true; - } - else - { - decSep = info.NumberDecimalSeparatorTChar(); - groupSep = info.NumberGroupSeparatorTChar(); - } - - int state = 0; - TChar* p = str; - uint ch = (p < strEnd) ? TChar.CastToUInt32(*p) : '\0'; - TChar* 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.PositiveSignTChar())) != 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.IsEmpty && (next = MatchChars(p, strEnd, currSymbol)) != null) - { - state |= StateCurrency; - currSymbol = ReadOnlySpan.Empty; - // 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 ? TChar.CastToUInt32(*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.NumberDecimalSeparatorTChar())) != 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.NumberGroupSeparatorTChar())) != null))) - { - p = next - 1; - } - else - { - break; - } - ch = ++p < strEnd ? TChar.CastToUInt32(*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)) - { - TChar* temp = p; - ch = ++p < strEnd ? TChar.CastToUInt32(*p) : '\0'; - if ((next = MatchChars(p, strEnd, info.PositiveSignTChar())) != null) - { - ch = (p = next) < strEnd ? TChar.CastToUInt32(*p) : '\0'; - } - else if ((next = MatchNegativeSignChars(p, strEnd, info)) != null) - { - ch = (p = next) < strEnd ? TChar.CastToUInt32(*p) : '\0'; - negExp = true; - } - if (IsDigit(ch)) - { - int exp = 0; - do - { - exp = (exp * 10) + (int)(ch - '0'); - ch = ++p < strEnd ? TChar.CastToUInt32(*p) : '\0'; - if (exp > 1000) - { - exp = 9999; - while (IsDigit(ch)) - { - ch = ++p < strEnd ? TChar.CastToUInt32(*p) : '\0'; - } - } - } while (IsDigit(ch)); - if (negExp) - { - exp = -exp; - } - number.Scale += exp; - } - else - { - p = temp; - ch = p < strEnd ? TChar.CastToUInt32(*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.PositiveSignTChar())) != 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.IsEmpty && (next = MatchChars(p, strEnd, currSymbol)) != null) - { - currSymbol = ReadOnlySpan.Empty; - p = next - 1; - } - else - { - break; - } - } - ch = ++p < strEnd ? TChar.CastToUInt32(*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; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ParsingStatus TryParseBinaryInteger(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out TInteger result) where TChar : unmanaged, IUtfChar @@ -537,7 +277,7 @@ internal static ParsingStatus TryParseBinaryIntegerStyle(ReadOn num = TChar.CastToUInt32(value[index]); } } - else if (info.AllowHyphenDuringParsing && num == '-') + else if (info.AllowHyphenDuringParsing() && num == '-') { isNegative = true; index++; @@ -1264,7 +1004,7 @@ internal static bool TryParseFloat(ReadOnlySpan value, Num return true; } - if (info.AllowHyphenDuringParsing && SpanStartsWith(valueTrim, TChar.CastFrom('-')) && SpanEqualsOrdinalIgnoreCase(valueTrim.Slice(1), nanSymbol)) + if (info.AllowHyphenDuringParsing() && SpanStartsWith(valueTrim, TChar.CastFrom('-')) && SpanEqualsOrdinalIgnoreCase(valueTrim.Slice(1), nanSymbol)) { result = TFloat.NaN; return true; @@ -1279,89 +1019,6 @@ internal static bool TryParseFloat(ReadOnlySpan value, Num return true; } - internal static unsafe bool TryStringToNumber(ReadOnlySpan value, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info) - where TChar : unmanaged, IUtfChar - { - Debug.Assert(info != null); - - fixed (TChar* stringPointer = &MemoryMarshal.GetReference(value)) - { - TChar* 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) - where TChar : unmanaged, IUtfChar - { - // For compatibility, we need to allow trailing zeros at the end of a number string - return !value.Slice(index).ContainsAnyExcept(TChar.CastFrom('\0')); - } - - private static bool IsSpaceReplacingChar(uint c) => (c == '\u00a0') || (c == '\u202f'); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe TChar* MatchNegativeSignChars(TChar* p, TChar* pEnd, NumberFormatInfo info) - where TChar : unmanaged, IUtfChar - { - TChar* ret = MatchChars(p, pEnd, info.NegativeSignTChar()); - - if ((ret is null) && info.AllowHyphenDuringParsing && (p < pEnd) && (TChar.CastToUInt32(*p) == '-')) - { - ret = p + 1; - } - - return ret; - } - - private static unsafe TChar* MatchChars(TChar* p, TChar* pEnd, ReadOnlySpan value) - where TChar : unmanaged, IUtfChar - { - Debug.Assert((p != null) && (pEnd != null) && (p <= pEnd) && (value != null)); - - fixed (TChar* stringPointer = &MemoryMarshal.GetReference(value)) - { - TChar* str = stringPointer; - - if (TChar.CastToUInt32(*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) - { - uint cp = (p < pEnd) ? TChar.CastToUInt32(*p) : '\0'; - uint val = TChar.CastToUInt32(*str); - - if ((cp != val) && !(IsSpaceReplacingChar(val) && (cp == '\u0020'))) - { - break; - } - - p++; - str++; - - if (TChar.CastToUInt32(*str) == '\0') - { - return p; - } - } - } - } - - return null; - } - [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 b5142e8ca5377..c09b75ddbc101 100644 --- a/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj +++ b/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj @@ -1,4 +1,4 @@ - + $(NetCoreAppCurrent) @@ -18,6 +18,7 @@ + diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs index c43eccf81f1a6..bb48901f31470 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -366,7 +366,7 @@ internal static unsafe ParsingStatus TryParseBigIntegerNumber(ReadOnlySpan { NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, buffer); - if (!TryStringToNumber(value, style, ref number, info)) + if (!TryStringToNumber(MemoryMarshal.Cast(value), style, ref number, info)) { result = default; ret = ParsingStatus.Failed; diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.Polyfill.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.Polyfill.cs new file mode 100644 index 0000000000000..8db22bd8a2d00 --- /dev/null +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.Polyfill.cs @@ -0,0 +1,121 @@ +// 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 +{ + // Polyfill CoreLib internal interfaces and methods + // Define necessary members only + + internal interface IUtfChar : + IEquatable + where TSelf : unmanaged, IUtfChar + { + public static abstract TSelf CastFrom(byte value); + + public static abstract TSelf CastFrom(char value); + + public static abstract TSelf CastFrom(int value); + + public static abstract TSelf CastFrom(uint value); + + public static abstract TSelf CastFrom(ulong value); + + public static abstract uint CastToUInt32(TSelf value); + } + +#pragma warning disable CA1067 // Polyfill only type + internal readonly struct Utf16Char(char ch) : IUtfChar +#pragma warning restore CA1067 + { + private readonly char value = ch; + + public static Utf16Char CastFrom(byte value) => new((char)value); + public static Utf16Char CastFrom(char value) => new(value); + public static Utf16Char CastFrom(int value) => new((char)value); + public static Utf16Char CastFrom(uint value) => new((char)value); + public static Utf16Char CastFrom(ulong value) => new((char)value); + public static uint CastToUInt32(Utf16Char value) => value.value; + public bool Equals(Utf16Char other) => value == other.value; + } + + internal static partial class Number + { + internal static bool AllowHyphenDuringParsing(this 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)] + internal static ReadOnlySpan PositiveSignTChar(this NumberFormatInfo info) + where TChar : unmanaged, IUtfChar + { + Debug.Assert(typeof(TChar) == typeof(Utf16Char)); + return MemoryMarshal.Cast(info.PositiveSign); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ReadOnlySpan NegativeSignTChar(this NumberFormatInfo info) + where TChar : unmanaged, IUtfChar + { + Debug.Assert(typeof(TChar) == typeof(Utf16Char)); + return MemoryMarshal.Cast(info.NegativeSign); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ReadOnlySpan CurrencySymbolTChar(this NumberFormatInfo info) + where TChar : unmanaged, IUtfChar + { + Debug.Assert(typeof(TChar) == typeof(Utf16Char)); + return MemoryMarshal.Cast(info.CurrencySymbol); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ReadOnlySpan CurrencyDecimalSeparatorTChar(this NumberFormatInfo info) + where TChar : unmanaged, IUtfChar + { + Debug.Assert(typeof(TChar) == typeof(Utf16Char)); + return MemoryMarshal.Cast(info.CurrencyDecimalSeparator); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ReadOnlySpan CurrencyGroupSeparatorTChar(this NumberFormatInfo info) + where TChar : unmanaged, IUtfChar + { + Debug.Assert(typeof(TChar) == typeof(Utf16Char)); + return MemoryMarshal.Cast(info.CurrencyGroupSeparator); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ReadOnlySpan NumberDecimalSeparatorTChar(this NumberFormatInfo info) + where TChar : unmanaged, IUtfChar + { + Debug.Assert(typeof(TChar) == typeof(Utf16Char)); + return MemoryMarshal.Cast(info.NumberDecimalSeparator); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ReadOnlySpan NumberGroupSeparatorTChar(this NumberFormatInfo info) + where TChar : unmanaged, IUtfChar + { + Debug.Assert(typeof(TChar) == typeof(Utf16Char)); + return MemoryMarshal.Cast(info.NumberGroupSeparator); + } + } +}