diff --git a/src/libraries/Common/src/System/Number.Parsing.Common.cs b/src/libraries/Common/src/System/Number.Parsing.Common.cs index cc3bc73c3d1236..a1618efff1dc31 100644 --- a/src/libraries/Common/src/System/Number.Parsing.Common.cs +++ b/src/libraries/Common/src/System/Number.Parsing.Common.cs @@ -10,11 +10,13 @@ namespace System { internal static partial class Number { - private static unsafe bool TryParseNumber(scoped ref TChar* str, TChar* strEnd, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info) + private static unsafe bool TryParseNumber(TChar* str, TChar* strEnd, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info, out int elementsConsumed) where TChar : unmanaged, IUtfChar { - Debug.Assert(str != null); - Debug.Assert(strEnd != null); + // str/strEnd may be null when the input is an empty span (e.g. default(ReadOnlySpan) + // originating from a null string), in which case they are both null and the range is empty. + Debug.Assert((str != null) || (str == strEnd)); + Debug.Assert((strEnd != null) || (str == strEnd)); Debug.Assert(str <= strEnd); Debug.Assert((styles & (NumberStyles.AllowHexSpecifier | NumberStyles.AllowBinarySpecifier)) == 0); @@ -269,41 +271,49 @@ private static unsafe bool TryParseNumber(scoped ref TChar* str, TChar* s number.IsNegative = false; } } - str = p; - return true; + + int index = (int)(p - str); + var value = new ReadOnlySpan(str, (int)(strEnd - str)); + + // 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 & NumberStyles.AllowTrailingInvalidCharacters) != 0)) + { + elementsConsumed = index; + return true; + } } } - str = p; + + elementsConsumed = 0; 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, out int elementsConsumed) 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; - } + bool succeeded = TryParseNumber(stringPointer, stringPointer + value.Length, styles, ref number, info, out elementsConsumed); + number.CheckConsistency(); + return succeeded; } - - 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) + private static int ConsumeTrailingNulls(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')); + // For compatibility, we need to allow trailing nulls at the end of a number string + var remainder = value.Slice(index); + + var nullsToConsume = remainder.IndexOfAnyExcept(TChar.CastFrom('\0')); + return index + ((nullsToConsume >= 0) ? nullsToConsume : remainder.Length); } private static bool IsWhite(uint ch) => (ch == 0x20) || ((ch - 0x09) <= (0x0D - 0x09)); @@ -339,7 +349,11 @@ internal enum ParsingStatus private static unsafe TChar* MatchChars(TChar* p, TChar* pEnd, ReadOnlySpan value) where TChar : unmanaged, IUtfChar { - Debug.Assert((p != null) && (pEnd != null) && (p <= pEnd)); + // p/pEnd may be null when the input being parsed is an empty span (e.g. from a null string), + // in which case they are both null and the range is empty; the loop below never dereferences p then. + Debug.Assert((p != null) || (p == pEnd)); + Debug.Assert((pEnd != null) || (p == pEnd)); + Debug.Assert(p <= pEnd); fixed (TChar* stringPointer = &MemoryMarshal.GetReference(value)) { diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index f4e58975f7479f..5e08d2f37f63a4 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -440,7 +440,7 @@ Invalid handle. - With the AllowHexSpecifier or AllowBinarySpecifier bit set in the enum bit field, the only other valid bits that can be combined into the enum value must be AllowLeadingWhite and AllowTrailingWhite. + With the AllowHexSpecifier or AllowBinarySpecifier bit set in the enum bit field, the only other valid bits that can be combined into the enum value must be AllowLeadingWhite, AllowTrailingWhite, and AllowTrailingInvalidCharacters. The NeutralResourcesLanguageAttribute on the assembly "{0}" specifies an invalid culture name: "{1}". @@ -4498,4 +4498,4 @@ Sequence contains no elements. - + \ No newline at end of file diff --git a/src/libraries/System.Private.CoreLib/src/System/Byte.cs b/src/libraries/System.Private.CoreLib/src/System/Byte.cs index e6a38afe6152b4..b835fde027b418 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Byte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Byte.cs @@ -124,19 +124,13 @@ public static byte Parse(ReadOnlySpan s, NumberStyles style = NumberStyles public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out byte result) { NumberFormatInfo.ValidateParseStyleInteger(style); - - if (s is null) - { - result = 0; - return false; - } - return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out byte result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } public override string ToString() @@ -1104,6 +1098,27 @@ static bool INumberBase.TryConvertToTruncating(byte value, [MaybeN } } + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out byte result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out byte result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out byte result, out int bytesConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed) == Number.ParsingStatus.OK; + } + // // IParsable // @@ -1176,7 +1191,7 @@ public static byte Parse(ReadOnlySpan utf8Text, NumberStyles style = Numbe public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out byte result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Char.cs b/src/libraries/System.Private.CoreLib/src/System/Char.cs index e0018e74006088..2ae543953c2087 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Char.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Char.cs @@ -1959,6 +1959,45 @@ static bool INumberBase.TryConvertToTruncating(char value, [MaybeN static bool INumberBase.TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out char result) => TryParse(s, out result); + static bool INumberBase.TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out char result, out int charsConsumed) + { + if (TryParse(s, out result)) + { + charsConsumed = 1; + return true; + } + + charsConsumed = 0; + return false; + } + + static bool INumberBase.TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out char result, out int charsConsumed) + { + if (TryParse(s, out result)) + { + charsConsumed = 1; + return true; + } + + charsConsumed = 0; + return false; + } + + static bool INumberBase.TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out char result, out int bytesConsumed) + { + if (Rune.DecodeFromUtf8(utf8Text, out Rune rune, out bytesConsumed) != Buffers.OperationStatus.Done || + bytesConsumed != utf8Text.Length || + !rune.IsBmp) + { + result = '\0'; + bytesConsumed = 0; + return false; + } + + result = (char)rune.Value; + return true; + } + // // IParsable // diff --git a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs index a5ff30b93be1b3..91988fc6e483f7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs @@ -551,19 +551,13 @@ public static decimal Parse(ReadOnlySpan s, NumberStyles style = NumberSty public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out decimal result) { NumberFormatInfo.ValidateParseStyleDecimal(style); - - if (s == null) - { - result = 0; - return false; - } - return Number.TryParseDecimal(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseDecimal(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out decimal result) { NumberFormatInfo.ValidateParseStyleDecimal(style); - return Number.TryParseDecimal(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseDecimal(s, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } // Returns a binary representation of a Decimal. The return value is an @@ -1781,6 +1775,27 @@ private static bool TryConvertTo(decimal value, [MaybeNullWhen(false)] o } } + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out decimal result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleDecimal(style); + return Number.TryParseDecimal(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out decimal result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleDecimal(style); + return Number.TryParseDecimal(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out decimal result, out int bytesConsumed) + { + NumberFormatInfo.ValidateParseStyleDecimal(style); + return Number.TryParseDecimal(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed) == Number.ParsingStatus.OK; + } + // // IParsable // @@ -1820,7 +1835,7 @@ public static decimal Parse(ReadOnlySpan utf8Text, NumberStyles style = Nu public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out decimal result) { NumberFormatInfo.ValidateParseStyleDecimal(style); - return Number.TryParseDecimal(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseDecimal(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Double.cs b/src/libraries/System.Private.CoreLib/src/System/Double.cs index 2ff9c3662e42d9..a305f338d17f3c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Double.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Double.cs @@ -427,19 +427,13 @@ public static double Parse(ReadOnlySpan s, NumberStyles style = NumberStyl public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out double result) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - - if (s == null) - { - result = 0; - return false; - } - return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _); } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out double result) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - return Number.TryParseFloat(s, style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseFloat(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); } // @@ -1487,6 +1481,27 @@ private static bool TryConvertTo(double value, [MaybeNullWhen(false)] ou } } + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out double result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed); + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out double result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + return Number.TryParseFloat(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed); + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out double result, out int bytesConsumed) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + return Number.TryParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed); + } + // // IParsable // @@ -2292,7 +2307,7 @@ public static double Parse(ReadOnlySpan utf8Text, NumberStyles style = Num public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out double result) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - return Number.TryParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out _); } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Enum.cs b/src/libraries/System.Private.CoreLib/src/System/Enum.cs index b25a1aa6bfdf47..e21515e05d3faf 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Enum.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Enum.cs @@ -941,7 +941,7 @@ private static bool TryParseByValueOrName( NumberFormatInfo numberFormat = NumberFormatInfo.InvariantInfo; const NumberStyles NumberStyle = NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingWhite; - Number.ParsingStatus status = Number.TryParseBinaryIntegerStyle(value, NumberStyle, numberFormat, out result); + Number.ParsingStatus status = Number.TryParseBinaryIntegerStyle(value, NumberStyle, numberFormat, out result, out _); if (status == Number.ParsingStatus.OK) { return true; 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 d8a700d825a32c..e4490535b1cd3e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs @@ -798,21 +798,21 @@ public static NumberFormatInfo ReadOnly(NumberFormatInfo nfi) return info; } - // private const NumberStyles InvalidNumberStyles = unchecked((NumberStyles) 0xFFFFFC00); + // private const NumberStyles InvalidNumberStyles = unchecked((NumberStyles) 0xFFFFF000); private const NumberStyles InvalidNumberStyles = ~(NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite | NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingSign | NumberStyles.AllowParentheses | NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands | NumberStyles.AllowExponent | NumberStyles.AllowCurrencySymbol | NumberStyles.AllowHexSpecifier - | NumberStyles.AllowBinarySpecifier); + | NumberStyles.AllowBinarySpecifier | NumberStyles.AllowTrailingInvalidCharacters); [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void ValidateParseStyleInteger(NumberStyles style) { - // Check for undefined flags or using AllowHexSpecifier/AllowBinarySpecifier each with anything other than AllowLeadingWhite/AllowTrailingWhite. + // Check for undefined flags or using AllowHexSpecifier/AllowBinarySpecifier each with anything other than AllowLeadingWhite/AllowTrailingWhite/AllowTrailingInvalidCharacters. if ((style & (InvalidNumberStyles | NumberStyles.AllowHexSpecifier | NumberStyles.AllowBinarySpecifier)) != 0 && - (style & ~NumberStyles.HexNumber) != 0 && - (style & ~NumberStyles.BinaryNumber) != 0) + (style & ~(NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters)) != 0 && + (style & ~(NumberStyles.BinaryNumber | NumberStyles.AllowTrailingInvalidCharacters)) != 0) { ThrowInvalid(style); diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberStyles.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberStyles.cs index cc5bb451b6c99d..edb5ac682de4a5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberStyles.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberStyles.cs @@ -60,6 +60,9 @@ public enum NumberStyles /// AllowBinarySpecifier = 0x00000400, + /// Indicates that the numeric string is allowed to contain otherwise invalid characters and that parsing should treat such characters as terminating the input. + AllowTrailingInvalidCharacters = 0x00000800, + Integer = AllowLeadingWhite | AllowTrailingWhite | AllowLeadingSign, HexNumber = AllowLeadingWhite | AllowTrailingWhite | AllowHexSpecifier, diff --git a/src/libraries/System.Private.CoreLib/src/System/Guid.cs b/src/libraries/System.Private.CoreLib/src/System/Guid.cs index 1ac2e1ccf24451..e807450ceeb633 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Guid.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Guid.cs @@ -591,7 +591,7 @@ static bool TryCompatParsing(ReadOnlySpan guidString, ref GuidResult resu result._fg = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness((ushort)uintTmp) : (ushort)uintTmp; // Unlike the other components, this one never allowed 0x or +, so we can parse it as straight hex. - if (Number.TryParseBinaryIntegerHexNumberStyle(guidString.Slice(28, 8), NumberStyles.AllowHexSpecifier, out uintTmp) == Number.ParsingStatus.OK) // _h, _i, _j, _k + if (Number.TryParseBinaryIntegerHexOrBinaryNumberStyle>(guidString.Slice(28, 8), NumberStyles.AllowHexSpecifier, out uintTmp, out _) == Number.ParsingStatus.OK) // _h, _i, _j, _k { result._hijk = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(uintTmp) : uintTmp; return true; diff --git a/src/libraries/System.Private.CoreLib/src/System/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs index 8fe7b7be916ddd..1b1a8557c8c104 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -392,13 +392,7 @@ public static Half Parse(ReadOnlySpan s, NumberStyles style = DefaultParse public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Half result) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - - if (s == null) - { - result = Zero; - return false; - } - return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _); } /// @@ -412,7 +406,7 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out Half result) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - return Number.TryParseFloat(s, style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseFloat(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); } private static bool AreZero(Half left, Half right) @@ -2167,6 +2161,27 @@ private static bool TryConvertTo(Half value, [MaybeNullWhen(false)] out } } + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Half result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed); + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out Half result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + return Number.TryParseFloat(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed); + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out Half result, out int bytesConsumed) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + return Number.TryParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed); + } + // // IParsable // @@ -2322,7 +2337,7 @@ public static Half Parse(ReadOnlySpan utf8Text, NumberStyles style = Numbe public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out Half result) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - return Number.TryParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out _); } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Int128.cs b/src/libraries/System.Private.CoreLib/src/System/Int128.cs index 5a829fbc7300e5..dfca98a773c392 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int128.cs @@ -157,19 +157,13 @@ public static Int128 Parse(ReadOnlySpan s, NumberStyles style = NumberStyl public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Int128 result) { NumberFormatInfo.ValidateParseStyleInteger(style); - - if (s is null) - { - result = 0; - return false; - } - return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out Int128 result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } // @@ -1915,6 +1909,27 @@ static bool INumberBase.TryConvertToTruncating(Int128 value, [Ma } } + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Int128 result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out Int128 result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out Int128 result, out int bytesConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed) == Number.ParsingStatus.OK; + } + // // IParsable // @@ -2110,7 +2125,7 @@ public static Int128 Parse(ReadOnlySpan utf8Text, NumberStyles style = Num public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out Int128 result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Int16.cs b/src/libraries/System.Private.CoreLib/src/System/Int16.cs index d7eb5b70c85789..c24d81e25f9d6a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int16.cs @@ -157,19 +157,13 @@ public static short Parse(ReadOnlySpan s, NumberStyles style = NumberStyle public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out short result) { NumberFormatInfo.ValidateParseStyleInteger(style); - - if (s is null) - { - result = 0; - return false; - } - return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out short result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } // @@ -1294,6 +1288,27 @@ static bool INumberBase.TryConvertToTruncating(short value, [Mayb } } + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out short result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out short result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out short result, out int bytesConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed) == Number.ParsingStatus.OK; + } + // // IParsable // @@ -1373,7 +1388,7 @@ public static short Parse(ReadOnlySpan utf8Text, NumberStyles style = Numb public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out short result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Int32.cs b/src/libraries/System.Private.CoreLib/src/System/Int32.cs index b6285e071a2e32..46a22c321911b8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int32.cs @@ -173,19 +173,13 @@ public static int Parse(ReadOnlySpan s, NumberStyles style = NumberStyles. public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out int result) { NumberFormatInfo.ValidateParseStyleInteger(style); - - if (s is null) - { - result = 0; - return false; - } - return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out int result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } // @@ -1361,6 +1355,27 @@ static bool INumberBase.TryConvertToTruncating(int value, [MaybeNul } } + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out int result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out int result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out int result, out int bytesConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed) == Number.ParsingStatus.OK; + } + // // IParsable // @@ -1440,7 +1455,7 @@ public static int Parse(ReadOnlySpan utf8Text, NumberStyles style = Number public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out int result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Int64.cs b/src/libraries/System.Private.CoreLib/src/System/Int64.cs index e5dbace947d36f..c3ada9fac1d2ed 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int64.cs @@ -170,19 +170,13 @@ public static long Parse(ReadOnlySpan s, NumberStyles style = NumberStyles public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out long result) { NumberFormatInfo.ValidateParseStyleInteger(style); - - if (s is null) - { - result = 0; - return false; - } - return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out long result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } // @@ -1364,6 +1358,27 @@ static bool INumberBase.TryConvertToTruncating(long value, [MaybeN } } + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out long result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out long result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out long result, out int bytesConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed) == Number.ParsingStatus.OK; + } + // // IParsable // @@ -1443,7 +1458,7 @@ public static long Parse(ReadOnlySpan utf8Text, NumberStyles style = Numbe public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out long result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs b/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs index 4a2b949814b413..100b2f154f354f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs @@ -1371,6 +1371,27 @@ static bool INumberBase.TryConvertToTruncating(nint value, [MaybeN } } + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out nint result, out int charsConsumed) + { + Unsafe.SkipInit(out result); + return nint_t.TryParse(s, style, provider, out Unsafe.As(ref result), out charsConsumed); + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out nint result, out int charsConsumed) + { + Unsafe.SkipInit(out result); + return nint_t.TryParse(s, style, provider, out Unsafe.As(ref result), out charsConsumed); + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out nint result, out int bytesConsumed) + { + Unsafe.SkipInit(out result); + return nint_t.TryParse(utf8Text, style, provider, out Unsafe.As(ref result), out bytesConsumed); + } + // // IShiftOperators // 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 b6e4ef477987cf..c0ffc894594296 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -253,7 +253,7 @@ internal static TInteger ParseBinaryInteger(ReadOnlySpan where TChar : unmanaged, IUtfChar where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo { - ParsingStatus status = TryParseBinaryInteger(value, styles, info, out TInteger result); + ParsingStatus status = TryParseBinaryInteger(value, styles, info, out TInteger result, out _); if (status != ParsingStatus.OK) { @@ -263,43 +263,44 @@ internal static TInteger ParseBinaryInteger(ReadOnlySpan } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static ParsingStatus TryParseBinaryInteger(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out TInteger result) + internal static ParsingStatus TryParseBinaryInteger(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out TInteger result, out int elementsConsumed) where TChar : unmanaged, IUtfChar where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo { - if ((styles & ~NumberStyles.Integer) == 0) + if ((styles & ~(NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters)) == 0) { // Optimized path for the common case of anything that's allowed for integer style. - return TryParseBinaryIntegerStyle(value, styles, info, out result); + return TryParseBinaryIntegerStyle(value, styles, info, out result, out elementsConsumed); } if ((styles & NumberStyles.AllowHexSpecifier) != 0) { - return TryParseBinaryIntegerHexNumberStyle(value, styles, out result); + return TryParseBinaryIntegerHexOrBinaryNumberStyle>(value, styles, out result, out elementsConsumed); } if ((styles & NumberStyles.AllowBinarySpecifier) != 0) { - return TryParseBinaryIntegerHexOrBinaryNumberStyle>(value, styles, out result); + return TryParseBinaryIntegerHexOrBinaryNumberStyle>(value, styles, out result, out elementsConsumed); } - return TryParseBinaryIntegerNumber(value, styles, info, out result); + return TryParseBinaryIntegerNumber(value, styles, info, out result, out elementsConsumed); } - private static unsafe ParsingStatus TryParseBinaryIntegerNumber(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out TInteger result) + private static ParsingStatus TryParseBinaryIntegerNumber(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out TInteger result, out int elementsConsumed) where TChar : unmanaged, IUtfChar where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo { result = TInteger.Zero; NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, stackalloc byte[TInteger.MaxDigitCount + 1]); - if (!TryStringToNumber(value, styles, ref number, info)) + if (!TryStringToNumber(value, styles, ref number, info, out elementsConsumed)) { return ParsingStatus.Failed; } if (!TryNumberBufferToBinaryInteger(ref number, ref result)) { + elementsConsumed = 0; return ParsingStatus.Overflow; } @@ -307,11 +308,11 @@ private static unsafe ParsingStatus TryParseBinaryIntegerNumber } /// Parses int limited to styles that make up NumberStyles.Integer. - internal static ParsingStatus TryParseBinaryIntegerStyle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out TInteger result) + internal static ParsingStatus TryParseBinaryIntegerStyle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out TInteger result, out int elementsConsumed) where TChar : unmanaged, IUtfChar where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo { - Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format"); + Debug.Assert((styles & ~(NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters)) == 0, "Only handles subsets of Integer format"); if (value.IsEmpty) { @@ -545,17 +546,31 @@ internal static ParsingStatus TryParseBinaryIntegerStyle(ReadOn 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 & NumberStyles.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; @@ -565,7 +580,7 @@ internal static ParsingStatus TryParseBinaryIntegerStyle(ReadOn { if ((styles & NumberStyles.AllowTrailingWhite) == 0) { - goto FalseExit; + goto InvalidExit; } for (index++; index < value.Length; index++) @@ -580,23 +595,10 @@ internal static ParsingStatus TryParseBinaryIntegerStyle(ReadOn if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; } - - if (!TrailingZeros(value, index)) - { - goto FalseExit; - } - goto DoneAtEndButPotentialOverflow; + goto InvalidExit; } - /// Parses limited to styles that make up NumberStyles.HexNumber. - internal static ParsingStatus TryParseBinaryIntegerHexNumberStyle(ReadOnlySpan value, NumberStyles styles, out TInteger result) - where TChar : unmanaged, IUtfChar - where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo - { - return TryParseBinaryIntegerHexOrBinaryNumberStyle>(value, styles, out result); - } - - private interface IHexOrBinaryParser + internal interface IHexOrBinaryParser where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo { static abstract NumberStyles AllowedStyles { get; } @@ -607,9 +609,9 @@ private interface IHexOrBinaryParser static abstract TInteger ShiftLeftForNextDigit(TInteger value); } - private readonly struct HexParser : IHexOrBinaryParser where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo + internal readonly struct HexParser : IHexOrBinaryParser where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo { - public static NumberStyles AllowedStyles => NumberStyles.HexNumber; + public static NumberStyles AllowedStyles => NumberStyles.HexNumber | NumberStyles.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; @@ -619,7 +621,7 @@ private interface IHexOrBinaryParser private readonly struct BinaryParser : IHexOrBinaryParser where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo { - public static NumberStyles AllowedStyles => NumberStyles.BinaryNumber; + public static NumberStyles AllowedStyles => NumberStyles.BinaryNumber | NumberStyles.AllowTrailingInvalidCharacters; public static bool IsValidChar(uint ch) => (ch - '0') <= 1; public static uint FromChar(uint ch) => ch - '0'; public static uint MaxDigitValue => 1; @@ -627,7 +629,7 @@ private interface IHexOrBinaryParser public static TInteger ShiftLeftForNextDigit(TInteger value) => value << 1; } - private static ParsingStatus TryParseBinaryIntegerHexOrBinaryNumberStyle(ReadOnlySpan value, NumberStyles styles, out TInteger result) + internal static ParsingStatus TryParseBinaryIntegerHexOrBinaryNumberStyle(ReadOnlySpan value, NumberStyles styles, out TInteger result, out int elementsConsumed) where TChar : unmanaged, IUtfChar where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo where TParser : struct, IHexOrBinaryParser @@ -747,17 +749,31 @@ private static ParsingStatus TryParseBinaryIntegerHexOrBinaryNumberStyle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) where TChar : unmanaged, IUtfChar { - ParsingStatus status = TryParseDecimal(value, styles, info, out decimal result); + ParsingStatus status = TryParseDecimal(value, styles, info, out decimal result, out _); + if (status != ParsingStatus.OK) { if (status == ParsingStatus.Failed) @@ -814,7 +826,7 @@ internal static TDecimal ParseDecimalIeee754(ReadOnlySp where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo where TValue : unmanaged, IBinaryInteger { - ParsingStatus status = TryParseDecimalIeee754(value, styles, info, out TDecimal result); + ParsingStatus status = TryParseDecimalIeee754(value, styles, info, out TDecimal result, out _); if (status == ParsingStatus.Failed) { @@ -948,34 +960,35 @@ internal static TFloat ParseFloat(ReadOnlySpan value, Numb where TChar : unmanaged, IUtfChar where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo { - if (!TryParseFloat(value, styles, info, out TFloat result)) + if (!TryParseFloat(value, styles, info, out TFloat result, out _)) { ThrowFormatException(value); } return result; } - internal static unsafe ParsingStatus TryParseDecimal(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out decimal result) + internal static unsafe ParsingStatus TryParseDecimal(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out decimal result, out int elementsConsumed) where TChar : unmanaged, IUtfChar { NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, stackalloc byte[DecimalNumberBufferLength]); result = 0; - if (!TryStringToNumber(value, styles, ref number, info)) + 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(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out TDecimal result) + internal static ParsingStatus TryParseDecimalIeee754(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out TDecimal result, out int elementsConsumed) where TChar : unmanaged, IUtfChar where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo where TValue : unmanaged, IBinaryInteger @@ -983,49 +996,65 @@ internal static ParsingStatus TryParseDecimalIeee754(Re NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, stackalloc byte[TDecimal.BufferLength]); result = default; - if (!TryStringToNumber(value, styles, ref number, info)) + if (!TryStringToNumber(value, styles, ref number, info, out elementsConsumed)) { ReadOnlySpan valueTrim = SpanTrim(value); + bool allowTrailingInvalid = (styles & NumberStyles.AllowTrailingInvalidCharacters) != 0; + + // For AllowTrailingInvalidCharacters we match a special-value symbol as a prefix of the + // trimmed input and report the leading whitespace plus the matched symbol as consumed. + // Otherwise the entire trimmed input must match the symbol exactly and the whole input + // (including any surrounding whitespace) is reported as consumed. + int leadingOffset = (allowTrailingInvalid && !valueTrim.IsEmpty) + ? (int)(Unsafe.ByteOffset(ref MemoryMarshal.GetReference(value), ref MemoryMarshal.GetReference(valueTrim)) / Unsafe.SizeOf()) + : 0; + ReadOnlySpan positiveInfinitySymbol = info.PositiveInfinitySymbolTChar(); - if (SpanEqualsOrdinalIgnoreCase(valueTrim, positiveInfinitySymbol)) + if (TryMatchSpecialValueSymbol(valueTrim, positiveInfinitySymbol, allowTrailingInvalid, out int matchedLength)) { result = TDecimal.Construct(TDecimal.PositiveInfinity); + elementsConsumed = allowTrailingInvalid ? leadingOffset + matchedLength : value.Length; return ParsingStatus.OK; } - if (SpanEqualsOrdinalIgnoreCase(valueTrim, info.NegativeInfinitySymbolTChar())) + if (TryMatchSpecialValueSymbol(valueTrim, info.NegativeInfinitySymbolTChar(), allowTrailingInvalid, out matchedLength)) { result = TDecimal.Construct(TDecimal.NegativeInfinity); + elementsConsumed = allowTrailingInvalid ? leadingOffset + matchedLength : value.Length; return ParsingStatus.OK; } ReadOnlySpan nanSymbol = info.NaNSymbolTChar(); - if (SpanEqualsOrdinalIgnoreCase(valueTrim, nanSymbol)) + if (TryMatchSpecialValueSymbol(valueTrim, nanSymbol, allowTrailingInvalid, out matchedLength)) { result = TDecimal.Construct(TDecimal.NaN); + elementsConsumed = allowTrailingInvalid ? leadingOffset + matchedLength : value.Length; return ParsingStatus.OK; } - var positiveSign = info.PositiveSignTChar(); + ReadOnlySpan positiveSign = info.PositiveSignTChar(); if (SpanStartsWith(valueTrim, positiveSign, StringComparison.OrdinalIgnoreCase)) { - valueTrim = valueTrim.Slice(positiveSign.Length); + ReadOnlySpan afterSign = valueTrim.Slice(positiveSign.Length); - if (SpanEqualsOrdinalIgnoreCase(valueTrim, positiveInfinitySymbol)) + if (TryMatchSpecialValueSymbol(afterSign, positiveInfinitySymbol, allowTrailingInvalid, out matchedLength)) { result = TDecimal.Construct(TDecimal.PositiveInfinity); + elementsConsumed = allowTrailingInvalid ? leadingOffset + positiveSign.Length + matchedLength : value.Length; return ParsingStatus.OK; } - else if (SpanEqualsOrdinalIgnoreCase(valueTrim, nanSymbol)) + else if (TryMatchSpecialValueSymbol(afterSign, nanSymbol, allowTrailingInvalid, out matchedLength)) { result = TDecimal.Construct(TDecimal.NaN); + elementsConsumed = allowTrailingInvalid ? leadingOffset + positiveSign.Length + matchedLength : value.Length; return ParsingStatus.OK; } result = TDecimal.Construct(TDecimal.Zero); + elementsConsumed = 0; return ParsingStatus.Failed; } @@ -1033,20 +1062,23 @@ internal static ParsingStatus TryParseDecimalIeee754(Re if (SpanStartsWith(valueTrim, negativeSign, StringComparison.OrdinalIgnoreCase)) { - if (SpanEqualsOrdinalIgnoreCase(valueTrim.Slice(negativeSign.Length), nanSymbol)) + if (TryMatchSpecialValueSymbol(valueTrim.Slice(negativeSign.Length), nanSymbol, allowTrailingInvalid, out matchedLength)) { result = TDecimal.Construct(TDecimal.NaN); + elementsConsumed = allowTrailingInvalid ? leadingOffset + negativeSign.Length + matchedLength : value.Length; return ParsingStatus.OK; } - if (info.AllowHyphenDuringParsing() && SpanStartsWith(valueTrim, TChar.CastFrom('-')) && SpanEqualsOrdinalIgnoreCase(valueTrim.Slice(1), nanSymbol)) + if (info.AllowHyphenDuringParsing() && SpanStartsWith(valueTrim, TChar.CastFrom('-')) && TryMatchSpecialValueSymbol(valueTrim.Slice(1), nanSymbol, allowTrailingInvalid, out matchedLength)) { result = TDecimal.Construct(TDecimal.NaN); + elementsConsumed = allowTrailingInvalid ? leadingOffset + 1 + matchedLength : value.Length; return ParsingStatus.OK; } } result = TDecimal.Construct(TDecimal.Zero); + elementsConsumed = 0; return ParsingStatus.Failed; } @@ -1116,7 +1148,31 @@ internal static bool SpanEqualsOrdinalIgnoreCase(ReadOnlySpan span } } - private static bool TryParseHexFloatingPoint(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out TFloat result) + // Matches a special-value symbol (Infinity/NaN and signed variants). When + // AllowTrailingInvalidCharacters is set the symbol only needs to be a prefix of the + // trimmed input; otherwise the entire trimmed input must match the symbol exactly. + internal static bool TryMatchSpecialValueSymbol(ReadOnlySpan valueTrim, ReadOnlySpan symbol, bool allowTrailingInvalid, out int matchedLength) + where TChar : unmanaged, IUtfChar + { + if (allowTrailingInvalid) + { + if (!symbol.IsEmpty && SpanStartsWith(valueTrim, symbol, StringComparison.OrdinalIgnoreCase)) + { + matchedLength = symbol.Length; + return true; + } + } + else if (SpanEqualsOrdinalIgnoreCase(valueTrim, symbol)) + { + matchedLength = symbol.Length; + return true; + } + + matchedLength = 0; + return false; + } + + internal static bool TryParseHexFloatingPoint(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out TFloat result, out int elementsConsumed) where TChar : unmanaged, IUtfChar where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo { @@ -1124,6 +1180,7 @@ private static bool TryParseHexFloatingPoint(ReadOnlySpan if (value.IsEmpty) { + elementsConsumed = 0; return false; } @@ -1140,6 +1197,7 @@ private static bool TryParseHexFloatingPoint(ReadOnlySpan if (index >= value.Length) { + elementsConsumed = 0; return false; } @@ -1170,6 +1228,7 @@ private static bool TryParseHexFloatingPoint(ReadOnlySpan if (index >= value.Length) { + elementsConsumed = 0; return false; } @@ -1178,12 +1237,14 @@ private static bool TryParseHexFloatingPoint(ReadOnlySpan 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; } @@ -1278,6 +1339,7 @@ private static bool TryParseHexFloatingPoint(ReadOnlySpan if (!hasIntegerPart && !hasFractionalPart) { + elementsConsumed = 0; return false; } @@ -1291,6 +1353,7 @@ private static bool TryParseHexFloatingPoint(ReadOnlySpan if (index >= value.Length) { + elementsConsumed = 0; return false; } @@ -1314,6 +1377,7 @@ private static bool TryParseHexFloatingPoint(ReadOnlySpan if (index >= value.Length) { + elementsConsumed = 0; return false; } @@ -1340,6 +1404,7 @@ private static bool TryParseHexFloatingPoint(ReadOnlySpan if (index == exponentStart) { + elementsConsumed = 0; return false; } @@ -1351,6 +1416,7 @@ private static bool TryParseHexFloatingPoint(ReadOnlySpan else { // Exponent indicator (p/P) is required + elementsConsumed = 0; return false; } @@ -1363,12 +1429,20 @@ private static bool TryParseHexFloatingPoint(ReadOnlySpan } } - // For compatibility, allow trailing null characters (same as other number parsers). - if (index != value.Length && !TrailingZeros(value, 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 & NumberStyles.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; @@ -1469,28 +1543,50 @@ private static bool TryParseHexFloatingPoint(ReadOnlySpan ulong bits = ((ulong)biasedExp << mantissaBits) | mantissa; result = TFloat.BitsToFloat(bits); + if (isNegative) { result = -result; } - return true; } - internal static unsafe bool TryParseFloat(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out TFloat result) + internal static unsafe bool TryParseFloat(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out TFloat result, out int elementsConsumed) where TChar : unmanaged, IUtfChar where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo { if ((styles & NumberStyles.AllowHexSpecifier) != 0) { - return TryParseHexFloatingPoint(value, styles, info, out result); + if (TryParseHexFloatingPoint(value, styles, info, out result, out elementsConsumed)) + { + return true; + } } + else + { + NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, stackalloc byte[TFloat.NumberBufferLength]); - NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, stackalloc byte[TFloat.NumberBufferLength]); + if (TryStringToNumber(value, styles, ref number, info, out elementsConsumed)) + { + result = NumberToFloat(ref number); + return true; + } + } - if (!TryStringToNumber(value, styles, ref number, info)) + // Hex floating-point does not support the Infinity/NaN symbols, so only the non-hex + // path falls through to the special-value handling below. + if ((styles & NumberStyles.AllowHexSpecifier) == 0) { ReadOnlySpan valueTrim = SpanTrim(value); + bool allowTrailingInvalid = (styles & NumberStyles.AllowTrailingInvalidCharacters) != 0; + + // For AllowTrailingInvalidCharacters we match a special-value symbol as a prefix of the + // trimmed input and report the leading whitespace plus the matched symbol as consumed. + // Otherwise the entire trimmed input must match the symbol exactly and the whole input + // (including any surrounding whitespace) is reported as consumed. + int leadingOffset = (allowTrailingInvalid && !valueTrim.IsEmpty) + ? (int)(Unsafe.ByteOffset(ref MemoryMarshal.GetReference(value), ref MemoryMarshal.GetReference(valueTrim)) / Unsafe.SizeOf()) + : 0; // 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` + @@ -1498,44 +1594,50 @@ internal static unsafe bool TryParseFloat(ReadOnlySpan val ReadOnlySpan positiveInfinitySymbol = info.PositiveInfinitySymbolTChar(); - if (SpanEqualsOrdinalIgnoreCase(valueTrim, positiveInfinitySymbol)) + if (TryMatchSpecialValueSymbol(valueTrim, positiveInfinitySymbol, allowTrailingInvalid, out int matchedLength)) { result = TFloat.PositiveInfinity; + elementsConsumed = allowTrailingInvalid ? leadingOffset + matchedLength : value.Length; return true; } - if (SpanEqualsOrdinalIgnoreCase(valueTrim, info.NegativeInfinitySymbolTChar())) + if (TryMatchSpecialValueSymbol(valueTrim, info.NegativeInfinitySymbolTChar(), allowTrailingInvalid, out matchedLength)) { result = TFloat.NegativeInfinity; + elementsConsumed = allowTrailingInvalid ? leadingOffset + matchedLength : value.Length; return true; } ReadOnlySpan nanSymbol = info.NaNSymbolTChar(); - if (SpanEqualsOrdinalIgnoreCase(valueTrim, nanSymbol)) + if (TryMatchSpecialValueSymbol(valueTrim, nanSymbol, allowTrailingInvalid, out matchedLength)) { result = TFloat.NaN; + elementsConsumed = allowTrailingInvalid ? leadingOffset + matchedLength : value.Length; return true; } - var positiveSign = info.PositiveSignTChar(); + ReadOnlySpan positiveSign = info.PositiveSignTChar(); if (SpanStartsWith(valueTrim, positiveSign, StringComparison.OrdinalIgnoreCase)) { - valueTrim = valueTrim.Slice(positiveSign.Length); + ReadOnlySpan afterSign = valueTrim.Slice(positiveSign.Length); - if (SpanEqualsOrdinalIgnoreCase(valueTrim, positiveInfinitySymbol)) + if (TryMatchSpecialValueSymbol(afterSign, positiveInfinitySymbol, allowTrailingInvalid, out matchedLength)) { result = TFloat.PositiveInfinity; + elementsConsumed = allowTrailingInvalid ? leadingOffset + positiveSign.Length + matchedLength : value.Length; return true; } - else if (SpanEqualsOrdinalIgnoreCase(valueTrim, nanSymbol)) + else if (TryMatchSpecialValueSymbol(afterSign, nanSymbol, allowTrailingInvalid, out matchedLength)) { result = TFloat.NaN; + elementsConsumed = allowTrailingInvalid ? leadingOffset + positiveSign.Length + matchedLength : value.Length; return true; } result = TFloat.Zero; + elementsConsumed = 0; return false; } @@ -1543,25 +1645,25 @@ internal static unsafe bool TryParseFloat(ReadOnlySpan val if (SpanStartsWith(valueTrim, negativeSign, StringComparison.OrdinalIgnoreCase)) { - if (SpanEqualsOrdinalIgnoreCase(valueTrim.Slice(negativeSign.Length), nanSymbol)) + if (TryMatchSpecialValueSymbol(valueTrim.Slice(negativeSign.Length), nanSymbol, allowTrailingInvalid, out matchedLength)) { result = TFloat.NaN; + elementsConsumed = allowTrailingInvalid ? leadingOffset + negativeSign.Length + matchedLength : value.Length; return true; } - if (info.AllowHyphenDuringParsing() && SpanStartsWith(valueTrim, TChar.CastFrom('-')) && SpanEqualsOrdinalIgnoreCase(valueTrim.Slice(1), nanSymbol)) + if (info.AllowHyphenDuringParsing() && SpanStartsWith(valueTrim, TChar.CastFrom('-')) && TryMatchSpecialValueSymbol(valueTrim.Slice(1), nanSymbol, allowTrailingInvalid, out matchedLength)) { result = TFloat.NaN; + elementsConsumed = allowTrailingInvalid ? leadingOffset + 1 + matchedLength : value.Length; return true; } } - - result = TFloat.Zero; - return false; // We really failed } - result = NumberToFloat(ref number); - return true; + result = TFloat.Zero; + elementsConsumed = 0; + return false; // We really failed } [DoesNotReturn] diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/BFloat16.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/BFloat16.cs index 356e3e19909150..37e6f3290244a5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/BFloat16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/BFloat16.cs @@ -308,13 +308,7 @@ public static BFloat16 Parse(ReadOnlySpan s, NumberStyles style = DefaultP public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out BFloat16 result) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - - if (s == null) - { - result = Zero; - return false; - } - return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _); } /// @@ -328,7 +322,7 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out BFloat16 result) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - return Number.TryParseFloat(s, style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseFloat(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); } // Comparison @@ -1941,6 +1935,27 @@ private static bool TryConvertTo(BFloat16 value, [MaybeNullWhen(false)] } } + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out BFloat16 result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed); + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out BFloat16 result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + return Number.TryParseFloat(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed); + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out BFloat16 result, out int bytesConsumed) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + return Number.TryParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed); + } + // // IParsable // @@ -2096,7 +2111,7 @@ public static BFloat16 Parse(ReadOnlySpan utf8Text, NumberStyles style = D public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out BFloat16 result) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - return Number.TryParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out _); } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs index 66dbf5a88ea481..721d96bcd297f9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs @@ -155,7 +155,7 @@ public static Decimal128 Parse(string s, NumberStyles style, IFormatProvider? pr public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal128 result) { NumberFormatInfo.ValidateParseStyleDecimal(style); - return Number.TryParseDecimalIeee754(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseDecimalIeee754(s, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } /// @@ -169,13 +169,7 @@ public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatPro public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal128 result) { NumberFormatInfo.ValidateParseStyleDecimal(style); - - if (s == null) - { - result = default; - return false; - } - return Number.TryParseDecimalIeee754(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseDecimalIeee754(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } /// @@ -252,6 +246,31 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin return Number.FormatDecimalIeee754(new UInt128(_upper, _lower), format, NumberFormatInfo.GetInstance(provider)); } + // + // INumberBase + // + + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Decimal128 result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleDecimal(style); + return Number.TryParseDecimalIeee754(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out Decimal128 result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleDecimal(style); + return Number.TryParseDecimalIeee754(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out Decimal128 result, out int bytesConsumed) + { + NumberFormatInfo.ValidateParseStyleDecimal(style); + return Number.TryParseDecimalIeee754(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed) == Number.ParsingStatus.OK; + } + private static readonly UInt128[] UInt128Powers10 = [ new UInt128(0, 1), diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs index 96818f968f75f3..d5f409cac95f4c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs @@ -159,7 +159,7 @@ public static Decimal32 Parse(string s, NumberStyles style, IFormatProvider? pro public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result) { NumberFormatInfo.ValidateParseStyleDecimal(style); - return Number.TryParseDecimalIeee754(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseDecimalIeee754(s, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } /// @@ -173,13 +173,7 @@ public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatPro public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result) { NumberFormatInfo.ValidateParseStyleDecimal(style); - - if (s == null) - { - result = default; - return false; - } - return Number.TryParseDecimalIeee754(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseDecimalIeee754(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } /// @@ -258,6 +252,31 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin return Number.FormatDecimalIeee754(_value, format, NumberFormatInfo.GetInstance(provider)); } + // + // INumberBase + // + + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Decimal32 result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleDecimal(style); + return Number.TryParseDecimalIeee754(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out Decimal32 result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleDecimal(style); + return Number.TryParseDecimalIeee754(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out Decimal32 result, out int bytesConsumed) + { + NumberFormatInfo.ValidateParseStyleDecimal(style); + return Number.TryParseDecimalIeee754(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed) == Number.ParsingStatus.OK; + } + static int IDecimalIeee754ParseAndFormatInfo.Precision => Precision; static int IDecimalIeee754ParseAndFormatInfo.BufferLength => Number.Decimal32NumberBufferLength; diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs index 0dd9a829786f1c..67f9806814b01b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs @@ -166,7 +166,7 @@ public static Decimal64 Parse(string s, NumberStyles style, IFormatProvider? pro public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal64 result) { NumberFormatInfo.ValidateParseStyleDecimal(style); - return Number.TryParseDecimalIeee754(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseDecimalIeee754(s, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } /// @@ -180,13 +180,7 @@ public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatPro public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal64 result) { NumberFormatInfo.ValidateParseStyleDecimal(style); - - if (s == null) - { - result = default; - return false; - } - return Number.TryParseDecimalIeee754(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseDecimalIeee754(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } /// @@ -259,6 +253,31 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin return Number.FormatDecimalIeee754(_value, format, NumberFormatInfo.GetInstance(provider)); } + // + // INumberBase + // + + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Decimal64 result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleDecimal(style); + return Number.TryParseDecimalIeee754(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out Decimal64 result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleDecimal(style); + return Number.TryParseDecimalIeee754(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out Decimal64 result, out int bytesConsumed) + { + NumberFormatInfo.ValidateParseStyleDecimal(style); + return Number.TryParseDecimalIeee754(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed) == Number.ParsingStatus.OK; + } + static int IDecimalIeee754ParseAndFormatInfo.Precision => Precision; static int IDecimalIeee754ParseAndFormatInfo.BufferLength => Number.Decimal64NumberBufferLength; diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs index 0f77f25426fb37..8acaa736d26689 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs @@ -419,6 +419,31 @@ protected static abstract bool TryConvertToTruncating(TSelf value, [Mayb /// is not a supported value. static abstract bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out TSelf result); + /// Tries to parse a string into a value. + /// The string to parse. + /// A bitwise combination of number styles that can be present in . + /// An object that provides culture-specific formatting information about . + /// On return, contains the result of successfully parsing or an undefined value on failure. + /// On return, contains the number of characters consumed during parsing. + /// true if was successfully parsed; otherwise, false. + /// is not a supported value. + static virtual bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out TSelf result, out int charsConsumed) + { + // NumberStyles.AllowTrailingInvalidCharacters is new with the introduction of this DIM and so the expected behavior of TryParse + // is to throw, meaning that we will always consume "everything" or "nothing". The style is passed through unmodified so a type + // that doesn't understand the flag throws as expected. Types which add support for the new flag are expected to override this + // method and provide a correct implementation. + + if (TSelf.TryParse(s, style, provider, out result)) + { + charsConsumed = s.Length; + return true; + } + + charsConsumed = 0; + return false; + } + /// Tries to parse a span of characters into a value. /// The span of characters to parse. /// A bitwise combination of number styles that can be present in . @@ -428,6 +453,31 @@ protected static abstract bool TryConvertToTruncating(TSelf value, [Mayb /// is not a supported value. static abstract bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out TSelf result); + /// Tries to parse a span of characters into a value. + /// The span of characters to parse. + /// A bitwise combination of number styles that can be present in . + /// An object that provides culture-specific formatting information about . + /// On return, contains the result of successfully parsing or an undefined value on failure. + /// On return, contains the number of characters consumed during parsing. + /// true if was successfully parsed; otherwise, false. + /// is not a supported value. + static virtual bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out TSelf result, out int charsConsumed) + { + // NumberStyles.AllowTrailingInvalidCharacters is new with the introduction of this DIM and so the expected behavior of TryParse + // is to throw, meaning that we will always consume "everything" or "nothing". The style is passed through unmodified so a type + // that doesn't understand the flag throws as expected. Types which add support for the new flag are expected to override this + // method and provide a correct implementation. + + if (TSelf.TryParse(s, style, provider, out result)) + { + charsConsumed = s.Length; + return true; + } + + charsConsumed = 0; + return false; + } + /// Tries to parse a span of UTF-8 characters into a value. /// The span of UTF-8 characters to parse. /// A bitwise combination of number styles that can be present in . @@ -483,6 +533,31 @@ static virtual unsafe bool TryParse(ReadOnlySpan utf8Text, NumberStyles st return succeeded; } + /// Tries to parse a span of UTF-8 characters into a value. + /// The span of UTF-8 characters to parse. + /// A bitwise combination of number styles that can be present in . + /// An object that provides culture-specific formatting information about . + /// On return, contains the result of successfully parsing or an undefined value on failure. + /// On return, contains the number of bytes consumed during parsing. + /// true if was successfully parsed; otherwise, false. + /// is not a supported value. + static virtual bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out TSelf result, out int bytesConsumed) + { + // NumberStyles.AllowTrailingInvalidCharacters is new with the introduction of this DIM and so the expected behavior of TryParse + // is to throw, meaning that we will always consume "everything" or "nothing". The style is passed through unmodified so a type + // that doesn't understand the flag throws as expected. Types which add support for the new flag are expected to override this + // method and provide a correct implementation. + + if (TSelf.TryParse(utf8Text, style, provider, out result)) + { + bytesConsumed = utf8Text.Length; + return true; + } + + bytesConsumed = 0; + return false; + } + unsafe bool IUtf8SpanFormattable.TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) { char[]? utf16DestinationArray; diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NFloat.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NFloat.cs index fd875adcc67b1f..30342a80c5ad83 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NFloat.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NFloat.cs @@ -1713,6 +1713,27 @@ private static bool TryConvertTo(NFloat value, [MaybeNullWhen(false)] ou } } + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out NFloat result, out int charsConsumed) + { + Unsafe.SkipInit(out result); + return NativeType.TryParse(s, style, provider, out Unsafe.As(ref result), out charsConsumed); + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out NFloat result, out int charsConsumed) + { + Unsafe.SkipInit(out result); + return NativeType.TryParse(s, style, provider, out Unsafe.As(ref result), out charsConsumed); + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out NFloat result, out int bytesConsumed) + { + Unsafe.SkipInit(out result); + return NativeType.TryParse(utf8Text, style, provider, out Unsafe.As(ref result), out bytesConsumed); + } + // // IParsable // diff --git a/src/libraries/System.Private.CoreLib/src/System/SByte.cs b/src/libraries/System.Private.CoreLib/src/System/SByte.cs index 48411fe9e54f94..be8a5afca401eb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SByte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SByte.cs @@ -160,19 +160,13 @@ public static sbyte Parse(ReadOnlySpan s, NumberStyles style = NumberStyle public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out sbyte result) { NumberFormatInfo.ValidateParseStyleInteger(style); - - if (s is null) - { - result = 0; - return false; - } - return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out sbyte result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } // @@ -1273,6 +1267,27 @@ static bool INumberBase.TryConvertToTruncating(sbyte value, [Mayb } } + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out sbyte result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out sbyte result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out sbyte result, out int bytesConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed) == Number.ParsingStatus.OK; + } + // // IParsable // @@ -1352,7 +1367,7 @@ public static sbyte Parse(ReadOnlySpan utf8Text, NumberStyles style = Numb public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out sbyte result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Single.cs b/src/libraries/System.Private.CoreLib/src/System/Single.cs index 7a106630b6c841..1f11f7f8ecd5fb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Single.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Single.cs @@ -422,19 +422,13 @@ public static float Parse(ReadOnlySpan s, NumberStyles style = NumberStyle public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out float result) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - - if (s == null) - { - result = 0; - return false; - } - return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _); } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out float result) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - return Number.TryParseFloat(s, style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseFloat(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); } // @@ -1506,6 +1500,27 @@ private static bool TryConvertTo(float value, [MaybeNullWhen(false)] out } } + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out float result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed); + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out float result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + return Number.TryParseFloat(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed); + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out float result, out int bytesConsumed) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + return Number.TryParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed); + } + // // IParsable // @@ -2208,7 +2223,7 @@ public static float Parse(ReadOnlySpan utf8Text, NumberStyles style = Numb public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out float result) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - return Number.TryParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out _); } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt128.cs b/src/libraries/System.Private.CoreLib/src/System/UInt128.cs index 4b82bb0f61e5e2..b66f13fc38bdea 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt128.cs @@ -159,19 +159,13 @@ public static UInt128 Parse(ReadOnlySpan s, NumberStyles style = NumberSty public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out UInt128 result) { NumberFormatInfo.ValidateParseStyleInteger(style); - - if (s is null) - { - result = 0; - return false; - } - return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out UInt128 result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } // @@ -2031,6 +2025,27 @@ static bool INumberBase.TryConvertToTruncating(UInt128 value, [ } } + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out UInt128 result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out UInt128 result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out UInt128 result, out int bytesConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed) == Number.ParsingStatus.OK; + } + // // IParsable // @@ -2183,7 +2198,7 @@ public static UInt128 Parse(ReadOnlySpan utf8Text, NumberStyles style = Nu public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out UInt128 result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt16.cs b/src/libraries/System.Private.CoreLib/src/System/UInt16.cs index e6cf2e20b51427..04d51ece490225 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt16.cs @@ -152,19 +152,13 @@ public static ushort Parse(ReadOnlySpan s, NumberStyles style = NumberStyl public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out ushort result) { NumberFormatInfo.ValidateParseStyleInteger(style); - - if (s is null) - { - result = 0; - return false; - } - return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out ushort result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } // @@ -1109,6 +1103,27 @@ static bool INumberBase.TryConvertToTruncating(ushort value, [Ma } } + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out ushort result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out ushort result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out ushort result, out int bytesConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed) == Number.ParsingStatus.OK; + } + // // IParsable // @@ -1181,7 +1196,7 @@ public static ushort Parse(ReadOnlySpan utf8Text, NumberStyles style = Num public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out ushort result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt32.cs b/src/libraries/System.Private.CoreLib/src/System/UInt32.cs index f8df052f77ebb0..8db7a767ae5b25 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt32.cs @@ -168,19 +168,13 @@ public static uint Parse(ReadOnlySpan s, NumberStyles style = NumberStyles public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out uint result) { NumberFormatInfo.ValidateParseStyleInteger(style); - - if (s is null) - { - result = 0; - return false; - } - return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out uint result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } // @@ -1180,6 +1174,27 @@ static bool INumberBase.TryConvertToTruncating(uint value, [MaybeN } } + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out uint result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out uint result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out uint result, out int bytesConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed) == Number.ParsingStatus.OK; + } + // // IParsable // @@ -1252,7 +1267,7 @@ public static uint Parse(ReadOnlySpan utf8Text, NumberStyles style = Numbe public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out uint result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt64.cs b/src/libraries/System.Private.CoreLib/src/System/UInt64.cs index b19deea580a6ea..71dd66c1e300cc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt64.cs @@ -167,19 +167,13 @@ public static ulong Parse(ReadOnlySpan s, NumberStyles style = NumberStyle public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out ulong result) { NumberFormatInfo.ValidateParseStyleInteger(style); - - if (s is null) - { - result = 0; - return false; - } - return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out ulong result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } // @@ -1181,6 +1175,27 @@ static bool INumberBase.TryConvertToTruncating(ulong value, [Mayb } } + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out ulong result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out ulong result, out int charsConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out ulong result, out int bytesConsumed) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed) == Number.ParsingStatus.OK; + } + // // IParsable // @@ -1253,7 +1268,7 @@ public static ulong Parse(ReadOnlySpan utf8Text, NumberStyles style = Numb public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out ulong result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs b/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs index 59de7b1f346dcc..ce140fe8a2b18e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs @@ -1170,6 +1170,27 @@ static bool INumberBase.TryConvertToTruncating(nuint value, [Mayb } } + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out nuint result, out int charsConsumed) + { + Unsafe.SkipInit(out result); + return nuint_t.TryParse(s, style, provider, out Unsafe.As(ref result), out charsConsumed); + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out nuint result, out int charsConsumed) + { + Unsafe.SkipInit(out result); + return nuint_t.TryParse(s, style, provider, out Unsafe.As(ref result), out charsConsumed); + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out nuint result, out int bytesConsumed) + { + Unsafe.SkipInit(out result); + return nuint_t.TryParse(utf8Text, style, provider, out Unsafe.As(ref result), out bytesConsumed); + } + // // IShiftOperators // diff --git a/src/libraries/System.Private.CoreLib/src/System/Version.cs b/src/libraries/System.Private.CoreLib/src/System/Version.cs index a268f0f6ca500a..9b8cf0ba33b560 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Version.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Version.cs @@ -430,7 +430,7 @@ static bool IUtf8SpanParsable.TryParse(ReadOnlySpan utf8Text, IFo private static bool TryParseComponent(ReadOnlySpan component, string componentName, bool throwOnFailure, ReadOnlySpan originalInput, out int parsedComponent) where TChar : unmanaged, IUtfChar { - Number.ParsingStatus parseStatus = Number.TryParseBinaryIntegerStyle(component, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out parsedComponent); + Number.ParsingStatus parseStatus = Number.TryParseBinaryIntegerStyle(component, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out parsedComponent, out _); if (parseStatus == Number.ParsingStatus.OK && parsedComponent >= 0) { diff --git a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs index c1660a7ca592ef..83525abeef366d 100644 --- a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs +++ b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs @@ -1573,6 +1573,9 @@ public static void Free(void* ptr) { } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Runtime.InteropServices.NFloat result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out System.Runtime.InteropServices.NFloat result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out System.Runtime.InteropServices.NFloat result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Runtime.InteropServices.NFloat result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Runtime.InteropServices.NFloat result, out int bytesConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Runtime.InteropServices.NFloat result, out int charsConsumed) { throw null; } } [System.AttributeUsageAttribute(System.AttributeTargets.Parameter, Inherited=false)] public sealed partial class OptionalAttribute : System.Attribute diff --git a/src/libraries/System.Runtime.Numerics/ref/System.Runtime.Numerics.cs b/src/libraries/System.Runtime.Numerics/ref/System.Runtime.Numerics.cs index e080d40f99ce11..b1f2ab889732bb 100644 --- a/src/libraries/System.Runtime.Numerics/ref/System.Runtime.Numerics.cs +++ b/src/libraries/System.Runtime.Numerics/ref/System.Runtime.Numerics.cs @@ -248,6 +248,9 @@ namespace System.Numerics public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? value, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.BigInteger result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out System.Numerics.BigInteger result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? value, out System.Numerics.BigInteger result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.BigInteger result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.BigInteger result, out int bytesConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.BigInteger result, out int charsConsumed) { throw null; } public bool TryWriteBytes(System.Span destination, out int bytesWritten, bool isUnsigned = false, bool isBigEndian = false) { throw null; } } public readonly partial struct Complex : System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.IUtf8SpanFormattable, System.IUtf8SpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators @@ -398,5 +401,8 @@ namespace System.Numerics public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out System.Numerics.Complex result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Complex result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out System.Numerics.Complex result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Complex result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Complex result, out int bytesConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Complex result, out int charsConsumed) { throw null; } } } diff --git a/src/libraries/System.Runtime.Numerics/src/Resources/Strings.resx b/src/libraries/System.Runtime.Numerics/src/Resources/Strings.resx index b9493813fa2800..34b00699194c40 100644 --- a/src/libraries/System.Runtime.Numerics/src/Resources/Strings.resx +++ b/src/libraries/System.Runtime.Numerics/src/Resources/Strings.resx @@ -66,8 +66,8 @@ An undefined NumberStyles value is being used. - - With the AllowHexSpecifier bit set in the enum bit field, the only other valid bits that can be combined into the enum value must be a subset of those in HexNumber. + + With the AllowHexSpecifier or AllowBinarySpecifier bit set in the enum bit field, the only other valid bits that can be combined into the enum value must be AllowLeadingWhite, AllowTrailingWhite, and AllowTrailingInvalidCharacters. '{0}' cannot be greater than {1}. @@ -114,4 +114,4 @@ Negative values do not have an unsigned representation. - + \ No newline at end of file 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 0175a048bdf2a5..69d33050fd6e23 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -21,7 +21,7 @@ internal static partial class Number | NumberStyles.AllowParentheses | NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands | NumberStyles.AllowExponent | NumberStyles.AllowCurrencySymbol | NumberStyles.AllowHexSpecifier - | NumberStyles.AllowBinarySpecifier); + | NumberStyles.AllowBinarySpecifier | NumberStyles.AllowTrailingInvalidCharacters); private static nuint[]? s_cachedPowersOf1e9; @@ -42,27 +42,20 @@ internal static void ThrowOverflowOrFormatException(ParsingStatus status) internal static bool TryValidateParseStyleInteger(NumberStyles style, [NotNullWhen(false)] out ArgumentException? e) { - // Check for undefined flags - if ((style & InvalidNumberStyles) != 0) + // Check for undefined flags or using AllowHexSpecifier/AllowBinarySpecifier each with anything other than AllowLeadingWhite/AllowTrailingWhite/AllowTrailingInvalidCharacters. + if ((style & (InvalidNumberStyles | NumberStyles.AllowHexSpecifier | NumberStyles.AllowBinarySpecifier)) != 0 && + (style & ~(NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters)) != 0 && + (style & ~(NumberStyles.BinaryNumber | NumberStyles.AllowTrailingInvalidCharacters)) != 0) { - e = new ArgumentException(SR.Argument_InvalidNumberStyles, nameof(style)); + e = new ArgumentException((style & InvalidNumberStyles) != 0 ? SR.Argument_InvalidNumberStyles : SR.Argument_InvalidHexBinaryStyle, nameof(style)); return false; } - if ((style & NumberStyles.AllowHexSpecifier) != 0) - { // Check for hex number - if ((style & ~NumberStyles.HexNumber) != 0) - { - e = new ArgumentException(SR.Argument_InvalidHexStyle, nameof(style)); - return false; - } - } - e = null; return true; } - internal static ParsingStatus TryParseBigInteger(ReadOnlySpan value, NumberStyles style, NumberFormatInfo info, out BigInteger result) + internal static ParsingStatus TryParseBigInteger(ReadOnlySpan value, NumberStyles style, NumberFormatInfo info, out BigInteger result, out int elementsConsumed) where TChar : unmanaged, IUtfChar { if (!TryValidateParseStyleInteger(style, out ArgumentException? e)) @@ -72,18 +65,18 @@ internal static ParsingStatus TryParseBigInteger(ReadOnlySpan valu if ((style & NumberStyles.AllowHexSpecifier) != 0) { - return TryParseBigIntegerHexOrBinaryNumberStyle, TChar>(value, style, out result); + return TryParseBigIntegerHexOrBinaryNumberStyle, TChar>(value, style, out result, out elementsConsumed); } if ((style & NumberStyles.AllowBinarySpecifier) != 0) { - return TryParseBigIntegerHexOrBinaryNumberStyle, TChar>(value, style, out result); + return TryParseBigIntegerHexOrBinaryNumberStyle, TChar>(value, style, out result, out elementsConsumed); } - return TryParseBigIntegerNumber(value, style, info, out result); + return TryParseBigIntegerNumber(value, style, info, out result, out elementsConsumed); } - internal static unsafe ParsingStatus TryParseBigIntegerNumber(ReadOnlySpan value, NumberStyles style, NumberFormatInfo info, out BigInteger result) + internal static unsafe ParsingStatus TryParseBigIntegerNumber(ReadOnlySpan value, NumberStyles style, NumberFormatInfo info, out BigInteger result, out int elementsConsumed) where TChar : unmanaged, IUtfChar { scoped Span buffer; @@ -92,6 +85,7 @@ internal static unsafe ParsingStatus TryParseBigIntegerNumber(ReadOnlySpa if (value.Length == 0) { result = default; + elementsConsumed = 0; return ParsingStatus.Failed; } @@ -110,7 +104,7 @@ internal static unsafe ParsingStatus TryParseBigIntegerNumber(ReadOnlySpa { NumberBuffer number = new(NumberBufferKind.Integer, buffer); - if (!TryStringToNumber(value, style, ref number, info)) + if (!TryStringToNumber(value, style, ref number, info, out elementsConsumed)) { result = default; ret = ParsingStatus.Failed; @@ -137,38 +131,41 @@ internal static BigInteger ParseBigInteger(ReadOnlySpan value, Num throw e; } - ParsingStatus status = TryParseBigInteger(value, style, info, out BigInteger result); + ParsingStatus status = TryParseBigInteger(value, style, info, out BigInteger result, out _); + if (status != ParsingStatus.OK) { ThrowOverflowOrFormatException(status); } - return result; } - internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle(ReadOnlySpan value, NumberStyles style, out BigInteger result) + internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle(ReadOnlySpan value, NumberStyles style, out BigInteger result, out int elementsConsumed) where TParser : struct, IBigIntegerHexOrBinaryParser where TChar : unmanaged, IUtfChar { - int whiteIndex; + int index = 0; + int trailingWhiteLength = 0; // Skip past any whitespace at the beginning. if ((style & NumberStyles.AllowLeadingWhite) != 0) { - for (whiteIndex = 0; whiteIndex < value.Length; whiteIndex++) + for (index = 0; index < value.Length; index++) { - if (!IsWhite(TChar.CastToUInt32(value[whiteIndex]))) + if (!IsWhite(TChar.CastToUInt32(value[index]))) { break; } } - value = value[whiteIndex..]; + value = value[index..]; } // Skip past any whitespace at the end. if ((style & NumberStyles.AllowTrailingWhite) != 0) { + int whiteIndex; + for (whiteIndex = value.Length - 1; whiteIndex >= 0; whiteIndex--) { if (!IsWhite(TChar.CastToUInt32(value[whiteIndex]))) @@ -176,8 +173,31 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle= 0 && int.MinValue < signedLeading && signedLeading <= int.MaxValue) { @@ -253,6 +278,7 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle BigInteger.MaxLength) { result = default; + elementsConsumed = 0; return ParsingStatus.Overflow; } @@ -263,6 +289,7 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle BigInteger.MaxLength) { result = default; + elementsConsumed = 0; return ParsingStatus.Overflow; } @@ -288,6 +316,7 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle static abstract nuint GetSignBitsIfValid(uint ch); + static abstract bool IsValidDigit(uint ch); + [MethodImpl(MethodImplOptions.AggressiveInlining)] static virtual bool TryParseUnalignedBlock(ReadOnlySpan input, out nuint result) { @@ -1575,6 +1608,9 @@ static virtual bool TryParseWholeBlocks(ReadOnlySpan input, Span d /// Returns all-zero bits if is a valid hex digit and considered positive ('0'-'7'), or all-one bits otherwise. public static nuint GetSignBitsIfValid(uint ch) => (nuint)(nint)((ch & 0b_1111_1000) == 0b_0011_0000 ? 0 : -1); + /// Returns if is a valid hex digit ('0'-'9', 'a'-'f', or 'A'-'F'). + public static bool IsValidDigit(uint ch) => ((ch - '0') <= ('9' - '0')) || (((ch | 0x20) - 'a') <= ('f' - 'a')); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool TryParseWholeBlocks(ReadOnlySpan input, Span destination) { @@ -1607,5 +1643,8 @@ public static bool TryParseWholeBlocks(ReadOnlySpan input, Span de /// Returns all-zero bits if is '0', or all-one bits if '1' (using LSB sign extension). public static nuint GetSignBitsIfValid(uint ch) => (nuint)(nint)(((int)ch << 31) >> 31); + + /// Returns if is a valid binary digit ('0' or '1'). + public static bool IsValidDigit(uint ch) => (ch - '0') <= ('1' - '0'); } } diff --git a/src/libraries/System.Runtime.Numerics/src/System/Number.Polyfill.cs b/src/libraries/System.Runtime.Numerics/src/System/Number.Polyfill.cs index 15996a2e38c2a2..cce131765786dc 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.Polyfill.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.Polyfill.cs @@ -84,24 +84,27 @@ internal static bool AllowHyphenDuringParsing(this NumberFormatInfo info) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool IsWhiteSpace(this ReadOnlySpan span) + internal static bool IsWhiteSpace(this ReadOnlySpan span, out int elementsConsumed) where TChar : unmanaged, IUtfChar { int elemsConsumed; for (int i = 0; i < span.Length; i += elemsConsumed) { - if (DecodeFromUtfChar(span, out Rune rune, out elemsConsumed) != OperationStatus.Done) + if (DecodeFromUtfChar(span[i..], out Rune rune, out elemsConsumed) != OperationStatus.Done) { + elementsConsumed = i; return false; } if (!Rune.IsWhiteSpace(rune)) { + elementsConsumed = i; return false; } } + elementsConsumed = span.Length; return true; } 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 fd345e7837b09a..9a7dccdcc4df8b 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs @@ -797,33 +797,33 @@ public bool IsPowerOfTwo public static BigInteger Parse(string value) { - return Parse(value, NumberStyles.Integer); + return Parse(value, NumberStyles.Integer, provider: null); } public static BigInteger Parse(string value, NumberStyles style) { - return Parse(value, style, NumberFormatInfo.CurrentInfo); + return Parse(value, style, provider: null); } public static BigInteger Parse(string value, IFormatProvider? provider) { - return Parse(value, NumberStyles.Integer, NumberFormatInfo.GetInstance(provider)); + return Parse(value, NumberStyles.Integer, provider); } public static BigInteger Parse(string value, NumberStyles style, IFormatProvider? provider) { ArgumentNullException.ThrowIfNull(value); - return Parse(value.AsSpan(), style, NumberFormatInfo.GetInstance(provider)); + return Parse(value.AsSpan(), style, provider); } public static bool TryParse([NotNullWhen(true)] string? value, out BigInteger result) { - return TryParse(value, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result); + return TryParse(value, NumberStyles.Integer, provider: null, out result); } public static bool TryParse([NotNullWhen(true)] string? value, NumberStyles style, IFormatProvider? provider, out BigInteger result) { - return TryParse(value.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result); + return TryParse(value.AsSpan(), style, provider, out result); } public static BigInteger Parse(ReadOnlySpan value, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) @@ -838,22 +838,40 @@ public static BigInteger Parse(ReadOnlySpan utf8Text, NumberStyles style = public static bool TryParse(ReadOnlySpan value, out BigInteger result) { - return TryParse(value, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result); + return TryParse(value, NumberStyles.Integer, provider: null, out result); } public static bool TryParse(ReadOnlySpan value, NumberStyles style, IFormatProvider? provider, out BigInteger result) { - return Number.TryParseBigInteger(MemoryMarshal.Cast(value), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBigInteger(MemoryMarshal.Cast(value), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan utf8Text, out BigInteger result) { - return TryParse(utf8Text, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result); + return TryParse(utf8Text, NumberStyles.Integer, provider: null, out result); } public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out BigInteger result) { - return Number.TryParseBigInteger(MemoryMarshal.Cast(utf8Text), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBigInteger(MemoryMarshal.Cast(utf8Text), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out BigInteger result, out int charsConsumed) + { + return TryParse(s.AsSpan(), style, provider, out result, out charsConsumed); + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out BigInteger result, out int bytesConsumed) + { + return Number.TryParseBigInteger(MemoryMarshal.Cast(utf8Text), style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed) == Number.ParsingStatus.OK; + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out BigInteger result, out int charsConsumed) + { + return Number.TryParseBigInteger(MemoryMarshal.Cast(s), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; } public static int Compare(BigInteger left, BigInteger right) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs index 71e6d335271783..029c956d8de52e 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs @@ -30,7 +30,8 @@ public readonly struct Complex | NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingSign | NumberStyles.AllowParentheses | NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands | NumberStyles.AllowExponent - | NumberStyles.AllowCurrencySymbol | NumberStyles.AllowHexSpecifier); + | NumberStyles.AllowCurrencySymbol | NumberStyles.AllowHexSpecifier + | NumberStyles.AllowTrailingInvalidCharacters); public static readonly Complex Zero = new(0.0, 0.0); public static readonly Complex One = new(1.0, 0.0); @@ -2130,13 +2131,25 @@ static bool INumberBase.TryConvertToTruncating(Complex value, [ /// public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out Complex result) - => TryParse(MemoryMarshal.Cast(s), style, provider, out result); + => TryParse(MemoryMarshal.Cast(s), style, provider, out result, out _); /// public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out Complex result) - => TryParse(MemoryMarshal.Cast(utf8Text), style, provider, out result); + => TryParse(MemoryMarshal.Cast(utf8Text), style, provider, out result, out _); - private static bool TryParse(ReadOnlySpan text, NumberStyles style, IFormatProvider? provider, out Complex result) + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Complex result, out int charsConsumed) + => TryParse(s.AsSpan(), style, provider, out result, out charsConsumed); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out Complex result, out int bytesConsumed) + => TryParse(MemoryMarshal.Cast(utf8Text), style, provider, out result, out bytesConsumed); + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out Complex result, out int charsConsumed) + => TryParse(MemoryMarshal.Cast(s), style, provider, out result, out charsConsumed); + + private static bool TryParse(ReadOnlySpan text, NumberStyles style, IFormatProvider? provider, out Complex result, out int elementsConsumed) where TChar : unmanaged, IUtfChar { ValidateParseStyleFloatingPoint(style); @@ -2151,25 +2164,33 @@ private static bool TryParse(ReadOnlySpan text, NumberStyles style // We also expect a to find an open bracket, a semicolon, and a closing bracket in that order result = default; + elementsConsumed = 0; return false; } - if ((openBracket != 0) && (((style & NumberStyles.AllowLeadingWhite) == 0) || !text.Slice(0, openBracket).IsWhiteSpace())) + if ((openBracket != 0) && (((style & NumberStyles.AllowLeadingWhite) == 0) || !text.Slice(0, openBracket).IsWhiteSpace(out _))) { // The opening bracket wasn't the first and we either didn't allow leading whitespace // or one of the leading characters wasn't whitespace at all. result = default; + elementsConsumed = 0; return false; } + // The real and imaginary components are exactly delimited by the ';' and '>' separators, + // so AllowTrailingInvalidCharacters only applies after the closing bracket, not within a + // component. Otherwise something like "<1.5x;2>" would incorrectly parse as (1.5, 2). + NumberStyles componentStyle = style & ~NumberStyles.AllowTrailingInvalidCharacters; + ReadOnlySpan slice = text.Slice(openBracket + 1, semicolon - openBracket - 1); if ((typeof(TChar) == typeof(Utf8Char)) - ? !double.TryParse(Unsafe.BitCast, ReadOnlySpan>(slice), style, provider, out double real) - : !double.TryParse(Unsafe.BitCast, ReadOnlySpan>(slice), style, provider, out real)) + ? !double.TryParse(Unsafe.BitCast, ReadOnlySpan>(slice), componentStyle, provider, out double real) + : !double.TryParse(Unsafe.BitCast, ReadOnlySpan>(slice), componentStyle, provider, out real)) { result = default; + elementsConsumed = 0; return false; } @@ -2186,23 +2207,41 @@ private static bool TryParse(ReadOnlySpan text, NumberStyles style slice = text.Slice(semicolon + 1, closeBracket - semicolon - 1); if ((typeof(TChar) == typeof(Utf8Char)) - ? !double.TryParse(Unsafe.BitCast, ReadOnlySpan>(slice), style, provider, out double imaginary) - : !double.TryParse(Unsafe.BitCast, ReadOnlySpan>(slice), style, provider, out imaginary)) + ? !double.TryParse(Unsafe.BitCast, ReadOnlySpan>(slice), componentStyle, provider, out double imaginary) + : !double.TryParse(Unsafe.BitCast, ReadOnlySpan>(slice), componentStyle, provider, out imaginary)) { result = default; + elementsConsumed = 0; return false; } - if ((closeBracket != (text.Length - 1)) && (((style & NumberStyles.AllowTrailingWhite) == 0) || !text.Slice(closeBracket).IsWhiteSpace())) + int trailingWhiteLength = 0; + + if (closeBracket != (text.Length - 1)) { - // The closing bracket wasn't the last and we either didn't allow trailing whitespace - // or one of the trailing characters wasn't whitespace at all. + bool isInvalid = true; - result = default; - return false; + if ((style & NumberStyles.AllowTrailingWhite) != 0) + { + if (text.Slice(closeBracket + 1).IsWhiteSpace(out trailingWhiteLength)) + { + isInvalid = false; + } + } + + if (isInvalid && ((style & NumberStyles.AllowTrailingInvalidCharacters) == 0)) + { + // The closing bracket wasn't the last and we either didn't allow trailing whitespace + // or one of the trailing characters wasn't whitespace at all. + + result = default; + elementsConsumed = 0; + return false; + } } result = new Complex(real, imaginary); + elementsConsumed = closeBracket + 1 + trailingWhiteLength; return true; } @@ -2228,11 +2267,6 @@ static void ThrowInvalid(NumberStyles value) /// public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Complex result) { - if (s is null) - { - result = default; - return false; - } return TryParse(s.AsSpan(), style, provider, out result); } diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs index 4f682c5216e7e9..13630859e1d006 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs @@ -1407,5 +1407,91 @@ public static void RegressionIssueRuntime94610(string text) parseTest.RegressionIssueRuntime94610(text); })); } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestData() + { + // Basic BigInteger parsing with trailing invalid characters + yield return new object[] { "123abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, new BigInteger(123), 3 }; + yield return new object[] { "456789xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, new BigInteger(456789), 6 }; + yield return new object[] { "0abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, BigInteger.Zero, 1 }; + + // With leading whitespace + yield return new object[] { " 123abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, new BigInteger(123), 5 }; + + // With signs + yield return new object[] { "+123abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, new BigInteger(123), 4 }; + yield return new object[] { "-456xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, new BigInteger(-456), 4 }; + + // HexNumber with trailing invalid characters + yield return new object[] { "ABCDEFxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, BigInteger.Parse("ABCDEF", NumberStyles.HexNumber), 6 }; + yield return new object[] { "FFGHxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, BigInteger.Parse("FF", NumberStyles.HexNumber), 2 }; + + // BinaryNumber with trailing invalid characters + yield return new object[] { "101010abc", NumberStyles.BinaryNumber | NumberStyles.AllowTrailingInvalidCharacters, null, BigInteger.Parse("101010", NumberStyles.BinaryNumber), 6 }; + yield return new object[] { "1112", NumberStyles.BinaryNumber | NumberStyles.AllowTrailingInvalidCharacters, null, BigInteger.Parse("111", NumberStyles.BinaryNumber), 3 }; + + // Large numbers with trailing characters + yield return new object[] { "123456789012345678901234567890abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, BigInteger.Parse("123456789012345678901234567890"), 30 }; + + // Valid number without trailing characters + yield return new object[] { "123456", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, new BigInteger(123456), 6 }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_TestData))] + public static void Parse_AllowTrailingInvalidCharacters(string value, NumberStyles style, IFormatProvider provider, BigInteger expectedValue, int expectedCharsConsumed) + { + BigInteger result; + int charsConsumed; + + // Test string overload with charsConsumed + Assert.True(BigInteger.TryParse(value, style, provider, out result, out charsConsumed)); + Assert.Equal(expectedValue, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + // Test ReadOnlySpan overload with charsConsumed + Assert.True(BigInteger.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(expectedValue, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + // Test UTF-8 overload with bytesConsumed + byte[] utf8Bytes = Encoding.UTF8.GetBytes(value); + int bytesConsumed; + Assert.True(BigInteger.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out bytesConsumed)); + Assert.Equal(expectedValue, result); + // For ASCII characters, bytes consumed should equal chars consumed + if (value.All(c => c < 128)) + { + Assert.Equal(expectedCharsConsumed, bytesConsumed); + } + } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_Invalid_TestData() + { + // Empty string + yield return new object[] { "", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Only invalid characters (no valid number) + yield return new object[] { "abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_Invalid_TestData))] + public static void Parse_AllowTrailingInvalidCharacters_Invalid(string value, NumberStyles style, IFormatProvider provider) + { + BigInteger result; + int charsConsumed; + + // Test string overload with charsConsumed + Assert.False(BigInteger.TryParse(value, style, provider, out result, out charsConsumed)); + Assert.Equal(BigInteger.Zero, result); + Assert.Equal(0, charsConsumed); + + // Test ReadOnlySpan overload with charsConsumed + Assert.False(BigInteger.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(BigInteger.Zero, result); + Assert.Equal(0, charsConsumed); + } } } diff --git a/src/libraries/System.Runtime.Numerics/tests/ComplexTests.cs b/src/libraries/System.Runtime.Numerics/tests/ComplexTests.cs index 5e1cf0fa21b0bf..59103dfd9eb872 100644 --- a/src/libraries/System.Runtime.Numerics/tests/ComplexTests.cs +++ b/src/libraries/System.Runtime.Numerics/tests/ComplexTests.cs @@ -201,6 +201,95 @@ public static void Parse(string valueScalar, NumberStyles style, IFormatProvider } } + public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestData() + { + // value, expectedReal, expectedImaginary, expectedCharsConsumed + yield return new object[] { "<1.5; 2.5>", 1.5, 2.5, 10 }; + yield return new object[] { "<1.5; 2.5>abc", 1.5, 2.5, 10 }; + yield return new object[] { "<0; 0>xyz", 0.0, 0.0, 6 }; + yield return new object[] { "<-1.5; -2.5>!!!", -1.5, -2.5, 12 }; + + // A trailing separator followed by another value stops after the first closing bracket. + yield return new object[] { "<100; 200>; <300; 400>", 100.0, 200.0, 10 }; + + // Leading whitespace is included in the consumed count (absolute index of the closing bracket). + yield return new object[] { " <1; 2>abc", 1.0, 2.0, 8 }; + + // Trailing whitespace before an invalid character is consumed when AllowTrailingWhite is set (part of NumberStyles.Float). + yield return new object[] { "<1; 2> x", 1.0, 2.0, 8 }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_TestData))] + public static void Parse_AllowTrailingInvalidCharacters(string value, double expectedReal, double expectedImaginary, int expectedCharsConsumed) + { + const NumberStyles Style = NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters; + IFormatProvider provider = CultureInfo.InvariantCulture; + + Complex expected = new Complex(expectedReal, expectedImaginary); + Complex result; + int charsConsumed; + + Assert.True(Complex.TryParse(value, Style, provider, out result, out charsConsumed)); + Assert.Equal(expected, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + Assert.True(Complex.TryParse(value.AsSpan(), Style, provider, out result, out charsConsumed)); + Assert.Equal(expected, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + byte[] utf8Value = Encoding.UTF8.GetBytes(value); + int bytesConsumed; + Assert.True(Complex.TryParse(utf8Value.AsSpan(), Style, provider, out result, out bytesConsumed)); + Assert.Equal(expected, result); + if (value.All(c => c < 128)) + { + Assert.Equal(expectedCharsConsumed, bytesConsumed); + } + } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_Invalid_TestData() + { + // Empty and non-complex inputs + yield return new object[] { "" }; + yield return new object[] { "not a complex" }; + + // Missing structural characters + yield return new object[] { "<1; 2" }; + yield return new object[] { "1; 2>" }; + yield return new object[] { "<1, 2>" }; + + // An invalid character within a component is never allowed, even with AllowTrailingInvalidCharacters, + // because the components are delimited by ';' and '>'. + yield return new object[] { "<1.5x; 2.5>" }; + yield return new object[] { "<1.5; 2.5x>" }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_Invalid_TestData))] + public static void Parse_AllowTrailingInvalidCharacters_Invalid(string value) + { + const NumberStyles Style = NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters; + IFormatProvider provider = CultureInfo.InvariantCulture; + + Complex result; + int charsConsumed; + + Assert.False(Complex.TryParse(value, Style, provider, out result, out charsConsumed)); + Assert.Equal(Complex.Zero, result); + Assert.Equal(0, charsConsumed); + + Assert.False(Complex.TryParse(value.AsSpan(), Style, provider, out result, out charsConsumed)); + Assert.Equal(Complex.Zero, result); + Assert.Equal(0, charsConsumed); + + byte[] utf8Value = Encoding.UTF8.GetBytes(value); + int bytesConsumed; + Assert.False(Complex.TryParse(utf8Value.AsSpan(), Style, provider, out result, out bytesConsumed)); + Assert.Equal(Complex.Zero, result); + Assert.Equal(0, bytesConsumed); + } + public static IEnumerable Valid_2_TestData() { foreach (double real in s_validDoubleValues) diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 4c23d358bdf940..c951c51c14b845 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -1073,6 +1073,9 @@ public static void SetByte(System.Array array, int index, byte value) { } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out byte result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out byte result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out byte result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out byte result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out byte result, out int bytesConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out byte result, out int charsConsumed) { throw null; } } public partial class CannotUnloadAppDomainException : System.SystemException { @@ -1242,6 +1245,9 @@ public CannotUnloadAppDomainException(string? message, System.Exception? innerEx static bool System.Numerics.INumberBase.TryConvertToTruncating(char value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } static bool System.Numerics.INumberBase.TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out char result) { throw null; } static bool System.Numerics.INumberBase.TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out char result) { throw null; } + static bool System.Numerics.INumberBase.TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out char result, out int charsConsumed) { throw null; } + static bool System.Numerics.INumberBase.TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out char result, out int bytesConsumed) { throw null; } + static bool System.Numerics.INumberBase.TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out char result, out int charsConsumed) { throw null; } static char System.Numerics.IShiftOperators.operator <<(char value, int shiftAmount) { throw null; } static char System.Numerics.IShiftOperators.operator >>(char value, int shiftAmount) { throw null; } static char System.Numerics.IShiftOperators.operator >>>(char value, int shiftAmount) { throw null; } @@ -2327,6 +2333,9 @@ void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Ser public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out decimal result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out decimal result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out decimal result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out decimal result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out decimal result, out int bytesConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out decimal result, out int charsConsumed) { throw null; } } public abstract partial class Delegate : System.ICloneable, System.Runtime.Serialization.ISerializable { @@ -2592,6 +2601,9 @@ public DivideByZeroException(string? message, System.Exception? innerException) public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out double result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out double result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out double result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out double result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out double result, out int bytesConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out double result, out int charsConsumed) { throw null; } } public partial class DuplicateWaitObjectException : System.ArgumentException { @@ -3456,6 +3468,9 @@ public enum GCNotificationStatus public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Half result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out System.Half result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out System.Half result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Half result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Half result, out int bytesConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Half result, out int charsConsumed) { throw null; } } public partial struct HashCode { @@ -3765,6 +3780,9 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Int128 result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out System.Int128 result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out System.Int128 result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Int128 result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Int128 result, out int bytesConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Int128 result, out int charsConsumed) { throw null; } } public readonly partial struct Int16 : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.IUtf8SpanFormattable, System.IUtf8SpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators { @@ -3906,6 +3924,9 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out short result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out short result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out short result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out short result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out short result, out int bytesConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out short result, out int charsConsumed) { throw null; } } public readonly partial struct Int32 : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.IUtf8SpanFormattable, System.IUtf8SpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators { @@ -4048,6 +4069,9 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out int result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out int result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out int result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out int result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out int result, out int bytesConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out int result, out int charsConsumed) { throw null; } } public readonly partial struct Int64 : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.IUtf8SpanFormattable, System.IUtf8SpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators { @@ -4190,6 +4214,9 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out long result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out long result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out long result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out long result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out long result, out int bytesConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out long result, out int charsConsumed) { throw null; } } public readonly partial struct IntPtr : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.IUtf8SpanFormattable, System.IUtf8SpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.Runtime.Serialization.ISerializable { @@ -4339,6 +4366,9 @@ void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Ser public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out nint result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out nint result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out nint result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out nint result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out nint result, out int bytesConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out nint result, out int charsConsumed) { throw null; } } public partial class InvalidCastException : System.SystemException { @@ -5353,6 +5383,9 @@ public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, S public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out sbyte result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out sbyte result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out sbyte result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out sbyte result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out sbyte result, out int bytesConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out sbyte result, out int charsConsumed) { throw null; } } [System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Delegate | System.AttributeTargets.Enum | System.AttributeTargets.Struct, Inherited=false)] [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] @@ -5564,6 +5597,9 @@ public SerializableAttribute() { } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out float result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out float result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out float result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out float result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out float result, out int bytesConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out float result, out int charsConsumed) { throw null; } } [System.Runtime.InteropServices.Marshalling.NativeMarshallingAttribute(typeof(System.Runtime.InteropServices.Marshalling.SpanMarshaller<,>))] public readonly ref partial struct Span @@ -7065,6 +7101,9 @@ public TypeUnloadedException(string? message, System.Exception? innerException) public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.UInt128 result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out System.UInt128 result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out System.UInt128 result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.UInt128 result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.UInt128 result, out int bytesConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.UInt128 result, out int charsConsumed) { throw null; } } [System.CLSCompliantAttribute(false)] public readonly partial struct UInt16 : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.IUtf8SpanFormattable, System.IUtf8SpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.Numerics.IUnsignedNumber @@ -7206,6 +7245,9 @@ public TypeUnloadedException(string? message, System.Exception? innerException) public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out ushort result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out ushort result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out ushort result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out ushort result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out ushort result, out int bytesConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out ushort result, out int charsConsumed) { throw null; } } [System.CLSCompliantAttribute(false)] public readonly partial struct UInt32 : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.IUtf8SpanFormattable, System.IUtf8SpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.Numerics.IUnsignedNumber @@ -7348,6 +7390,9 @@ public TypeUnloadedException(string? message, System.Exception? innerException) public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out uint result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out uint result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out uint result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out uint result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out uint result, out int bytesConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out uint result, out int charsConsumed) { throw null; } } [System.CLSCompliantAttribute(false)] public readonly partial struct UInt64 : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.IUtf8SpanFormattable, System.IUtf8SpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.Numerics.IUnsignedNumber @@ -7490,6 +7535,9 @@ public TypeUnloadedException(string? message, System.Exception? innerException) public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out ulong result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out ulong result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out ulong result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out ulong result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out ulong result, out int bytesConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out ulong result, out int charsConsumed) { throw null; } } [System.CLSCompliantAttribute(false)] public readonly partial struct UIntPtr : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.IUtf8SpanFormattable, System.IUtf8SpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.Numerics.IUnsignedNumber, System.Runtime.Serialization.ISerializable @@ -7635,6 +7683,9 @@ void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Ser public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out nuint result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out nuint result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out nuint result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out nuint result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out nuint result, out int bytesConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out nuint result, out int charsConsumed) { throw null; } } public partial class UnauthorizedAccessException : System.SystemException { @@ -9829,6 +9880,7 @@ public enum NumberStyles HexFloat = 679, AllowBinarySpecifier = 1024, BinaryNumber = 1027, + AllowTrailingInvalidCharacters = 2048, } public partial class PersianCalendar : System.Globalization.Calendar { @@ -11401,6 +11453,9 @@ public readonly struct Decimal32 public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? s, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out Decimal32 result) { throw null; } public static bool TryParse(ReadOnlySpan s, System.Globalization.NumberStyles style, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out Decimal32 result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? s, System.Globalization.NumberStyles style, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out Decimal32 result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Decimal32 result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Decimal32 result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Decimal32 result, out int bytesConsumed) { throw null; } } public readonly struct Decimal64 @@ -11442,6 +11497,9 @@ public readonly struct Decimal64 public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? s, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out Decimal64 result) { throw null; } public static bool TryParse(ReadOnlySpan s, System.Globalization.NumberStyles style, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out Decimal64 result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? s, System.Globalization.NumberStyles style, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out Decimal64 result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Decimal64 result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Decimal64 result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Decimal64 result, out int bytesConsumed) { throw null; } } public readonly struct Decimal128 @@ -11483,6 +11541,9 @@ public readonly struct Decimal128 public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? s, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out Decimal128 result) { throw null; } public static bool TryParse(ReadOnlySpan s, System.Globalization.NumberStyles style, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out Decimal128 result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? s, System.Globalization.NumberStyles style, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out Decimal128 result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Decimal128 result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Decimal128 result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Decimal128 result, out int bytesConsumed) { throw null; } } public readonly partial struct BFloat16 : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.IUtf8SpanFormattable, System.IUtf8SpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryFloatingPointIeee754, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IExponentialFunctions, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IFloatingPointIeee754, System.Numerics.IHyperbolicFunctions, System.Numerics.IIncrementOperators, System.Numerics.ILogarithmicFunctions, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IPowerFunctions, System.Numerics.IRootFunctions, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.ITrigonometricFunctions, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators @@ -11723,6 +11784,9 @@ public readonly struct Decimal128 public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.BFloat16 result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out System.Numerics.BFloat16 result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out System.Numerics.BFloat16 result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.BFloat16 result, out int charsConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.BFloat16 result, out int bytesConsumed) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.BFloat16 result, out int charsConsumed) { throw null; } } public static partial class BitOperations @@ -12057,6 +12121,9 @@ protected static abstract bool TryConvertToTruncating(TSelf value, [Syst static virtual bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TSelf result) { throw null; } static abstract bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TSelf result); static abstract bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TSelf result); + static virtual bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TSelf result, out int charsConsumed) { throw null; } + static virtual bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TSelf result, out int bytesConsumed) { throw null; } + static virtual bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TSelf result, out int charsConsumed) { throw null; } } public partial interface INumber : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumberBase, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.INumber? { diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ByteTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ByteTests.cs index d95fd2c3d8666f..5fcba193bb7f85 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ByteTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ByteTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Text; using Xunit; @@ -429,5 +430,79 @@ public static void Parse_Utf8Span_InvalidUtf8() [MemberData(nameof(ToString_TestData))] public static void TryFormat(byte i, string format, IFormatProvider provider, string expected) => NumberFormatTestHelper.TryFormatNumberTest(i, format, provider, expected); + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestData() + { + yield return new object[] { "123abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, (byte)123, 3 }; + yield return new object[] { "12xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, (byte)12, 2 }; + yield return new object[] { "0abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, (byte)0, 1 }; + yield return new object[] { "255abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, (byte)255, 3 }; + yield return new object[] { "FFxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, (byte)0xFF, 2 }; + yield return new object[] { "ABGxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, (byte)0xAB, 2 }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_TestData))] + public static void Parse_AllowTrailingInvalidCharacters(string value, NumberStyles style, IFormatProvider provider, byte expectedValue, int expectedCharsConsumed) + { + byte result; + int charsConsumed; + + Assert.True(byte.TryParse(value, style, provider, out result, out charsConsumed)); + Assert.Equal(expectedValue, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + Assert.True(byte.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(expectedValue, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + byte[] utf8Bytes = Encoding.UTF8.GetBytes(value); + int bytesConsumed; + Assert.True(byte.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out bytesConsumed)); + Assert.Equal(expectedValue, result); + if (value.All(c => c < 128)) + { + Assert.Equal(expectedCharsConsumed, bytesConsumed); + } + } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_Invalid_TestData() + { + yield return new object[] { "", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { " ", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "!!!", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { ".123", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Overflow of the leading valid digits + yield return new object[] { "256abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "999abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Invalid hex/binary starting characters + yield return new object[] { "Gxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "2abc", NumberStyles.BinaryNumber | NumberStyles.AllowTrailingInvalidCharacters, null }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_Invalid_TestData))] + public static void Parse_AllowTrailingInvalidCharacters_Invalid(string value, NumberStyles style, IFormatProvider provider) + { + byte result; + int charsConsumed; + + Assert.False(byte.TryParse(value, style, provider, out result, out charsConsumed)); + Assert.Equal(0, result); + Assert.Equal(0, charsConsumed); + + Assert.False(byte.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(0, result); + Assert.Equal(0, charsConsumed); + + byte[] utf8Bytes = Encoding.UTF8.GetBytes(value); + int bytesConsumed; + Assert.False(byte.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out bytesConsumed)); + Assert.Equal(0, result); + Assert.Equal(0, bytesConsumed); + } } } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs index 8456d115035fbd..37e20112255d6a 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs @@ -502,5 +502,62 @@ public static IEnumerable NaN_NonCanonicalEncodings128_TestData() yield return new object[] { Decimal128.NaN, canonical | (UInt128)0xFFFFF }; yield return new object[] { Decimal128.NaN, canonical | (((UInt128)1 << 110) - 1) }; } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestData() + { + NumberStyles style = NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters; + + // Trailing invalid characters after a valid number + yield return new object[] { "123abc", style, CultureInfo.InvariantCulture, Decimal128.Parse("123", CultureInfo.InvariantCulture), 3 }; + yield return new object[] { "12.5xyz", style, CultureInfo.InvariantCulture, Decimal128.Parse("12.5", CultureInfo.InvariantCulture), 4 }; + yield return new object[] { "+7e2!!", style, CultureInfo.InvariantCulture, Decimal128.Parse("7e2", CultureInfo.InvariantCulture), 4 }; + yield return new object[] { "-8.0#", style, CultureInfo.InvariantCulture, Decimal128.Parse("-8.0", CultureInfo.InvariantCulture), 4 }; + + // No trailing invalid characters + yield return new object[] { "123", style, CultureInfo.InvariantCulture, Decimal128.Parse("123", CultureInfo.InvariantCulture), 3 }; + + // Special values with trailing invalid characters + yield return new object[] { "Infinityabc", style, CultureInfo.InvariantCulture, Decimal128.PositiveInfinity, 8 }; + yield return new object[] { "-Infinityxyz", style, CultureInfo.InvariantCulture, Decimal128.NegativeInfinity, 9 }; + yield return new object[] { "NaNabc", style, CultureInfo.InvariantCulture, Decimal128.NaN, 3 }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_TestData))] + public static void Parse_AllowTrailingInvalidCharacters(string value, NumberStyles style, IFormatProvider provider, Decimal128 expected, int expectedCharsConsumed) + { + Assert.True(Decimal128.TryParse(value, style, provider, out Decimal128 result, out int charsConsumed)); + Assert.Equal(expected, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + Assert.True(Decimal128.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(expected, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(value); + Assert.True(Decimal128.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out int bytesConsumed)); + Assert.Equal(expected, result); + Assert.Equal(expectedCharsConsumed, bytesConsumed); + } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_Invalid_TestData() + { + NumberStyles style = NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters; + + yield return new object[] { "", style, CultureInfo.InvariantCulture }; + yield return new object[] { "abc", style, CultureInfo.InvariantCulture }; + yield return new object[] { ".abc", style, CultureInfo.InvariantCulture }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_Invalid_TestData))] + public static void Parse_AllowTrailingInvalidCharacters_Invalid(string value, NumberStyles style, IFormatProvider provider) + { + Assert.False(Decimal128.TryParse(value, style, provider, out Decimal128 result, out int charsConsumed)); + Assert.Equal(0, charsConsumed); + + Assert.False(Decimal128.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(0, charsConsumed); + } } } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs index e62c9c53359350..9e34263323c659 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs @@ -498,5 +498,62 @@ public static IEnumerable NaN_NonCanonicalEncodings_TestData() yield return new object[] { Decimal32.NaN, canonical | 0x000F_FFFFU }; yield return new object[] { Decimal32.NaN, canonical | 0x03FF_FFFFU }; } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestData() + { + NumberStyles style = NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters; + + // Trailing invalid characters after a valid number + yield return new object[] { "123abc", style, CultureInfo.InvariantCulture, Decimal32.Parse("123", CultureInfo.InvariantCulture), 3 }; + yield return new object[] { "12.5xyz", style, CultureInfo.InvariantCulture, Decimal32.Parse("12.5", CultureInfo.InvariantCulture), 4 }; + yield return new object[] { "+7e2!!", style, CultureInfo.InvariantCulture, Decimal32.Parse("7e2", CultureInfo.InvariantCulture), 4 }; + yield return new object[] { "-8.0#", style, CultureInfo.InvariantCulture, Decimal32.Parse("-8.0", CultureInfo.InvariantCulture), 4 }; + + // No trailing invalid characters + yield return new object[] { "123", style, CultureInfo.InvariantCulture, Decimal32.Parse("123", CultureInfo.InvariantCulture), 3 }; + + // Special values with trailing invalid characters + yield return new object[] { "Infinityabc", style, CultureInfo.InvariantCulture, Decimal32.PositiveInfinity, 8 }; + yield return new object[] { "-Infinityxyz", style, CultureInfo.InvariantCulture, Decimal32.NegativeInfinity, 9 }; + yield return new object[] { "NaNabc", style, CultureInfo.InvariantCulture, Decimal32.NaN, 3 }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_TestData))] + public static void Parse_AllowTrailingInvalidCharacters(string value, NumberStyles style, IFormatProvider provider, Decimal32 expected, int expectedCharsConsumed) + { + Assert.True(Decimal32.TryParse(value, style, provider, out Decimal32 result, out int charsConsumed)); + Assert.Equal(expected, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + Assert.True(Decimal32.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(expected, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(value); + Assert.True(Decimal32.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out int bytesConsumed)); + Assert.Equal(expected, result); + Assert.Equal(expectedCharsConsumed, bytesConsumed); + } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_Invalid_TestData() + { + NumberStyles style = NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters; + + yield return new object[] { "", style, CultureInfo.InvariantCulture }; + yield return new object[] { "abc", style, CultureInfo.InvariantCulture }; + yield return new object[] { ".abc", style, CultureInfo.InvariantCulture }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_Invalid_TestData))] + public static void Parse_AllowTrailingInvalidCharacters_Invalid(string value, NumberStyles style, IFormatProvider provider) + { + Assert.False(Decimal32.TryParse(value, style, provider, out Decimal32 result, out int charsConsumed)); + Assert.Equal(0, charsConsumed); + + Assert.False(Decimal32.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(0, charsConsumed); + } } } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs index 6258b5512558f0..4c73fcc894eacc 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs @@ -504,5 +504,62 @@ public static IEnumerable NaN_NonCanonicalEncodings64_TestData() yield return new object[] { Decimal64.NaN, canonical | 0x0000_0000_000F_FFFFUL }; yield return new object[] { Decimal64.NaN, canonical | 0x03FF_FFFF_FFFF_FFFFUL }; } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestData() + { + NumberStyles style = NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters; + + // Trailing invalid characters after a valid number + yield return new object[] { "123abc", style, CultureInfo.InvariantCulture, Decimal64.Parse("123", CultureInfo.InvariantCulture), 3 }; + yield return new object[] { "12.5xyz", style, CultureInfo.InvariantCulture, Decimal64.Parse("12.5", CultureInfo.InvariantCulture), 4 }; + yield return new object[] { "+7e2!!", style, CultureInfo.InvariantCulture, Decimal64.Parse("7e2", CultureInfo.InvariantCulture), 4 }; + yield return new object[] { "-8.0#", style, CultureInfo.InvariantCulture, Decimal64.Parse("-8.0", CultureInfo.InvariantCulture), 4 }; + + // No trailing invalid characters + yield return new object[] { "123", style, CultureInfo.InvariantCulture, Decimal64.Parse("123", CultureInfo.InvariantCulture), 3 }; + + // Special values with trailing invalid characters + yield return new object[] { "Infinityabc", style, CultureInfo.InvariantCulture, Decimal64.PositiveInfinity, 8 }; + yield return new object[] { "-Infinityxyz", style, CultureInfo.InvariantCulture, Decimal64.NegativeInfinity, 9 }; + yield return new object[] { "NaNabc", style, CultureInfo.InvariantCulture, Decimal64.NaN, 3 }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_TestData))] + public static void Parse_AllowTrailingInvalidCharacters(string value, NumberStyles style, IFormatProvider provider, Decimal64 expected, int expectedCharsConsumed) + { + Assert.True(Decimal64.TryParse(value, style, provider, out Decimal64 result, out int charsConsumed)); + Assert.Equal(expected, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + Assert.True(Decimal64.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(expected, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(value); + Assert.True(Decimal64.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out int bytesConsumed)); + Assert.Equal(expected, result); + Assert.Equal(expectedCharsConsumed, bytesConsumed); + } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_Invalid_TestData() + { + NumberStyles style = NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters; + + yield return new object[] { "", style, CultureInfo.InvariantCulture }; + yield return new object[] { "abc", style, CultureInfo.InvariantCulture }; + yield return new object[] { ".abc", style, CultureInfo.InvariantCulture }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_Invalid_TestData))] + public static void Parse_AllowTrailingInvalidCharacters_Invalid(string value, NumberStyles style, IFormatProvider provider) + { + Assert.False(Decimal64.TryParse(value, style, provider, out Decimal64 result, out int charsConsumed)); + Assert.Equal(0, charsConsumed); + + Assert.False(Decimal64.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(0, charsConsumed); + } } } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DecimalTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DecimalTests.cs index 092001a946e6d1..080fa1a289d200 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DecimalTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DecimalTests.cs @@ -2434,5 +2434,93 @@ public static void TryFormat() } } } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestData() + { + // Basic decimal parsing with trailing invalid characters + yield return new object[] { "123.45abc", NumberStyles.Number | NumberStyles.AllowTrailingInvalidCharacters, null, 123.45m, 6 }; + yield return new object[] { "456.78xyz", NumberStyles.Number | NumberStyles.AllowTrailingInvalidCharacters, null, 456.78m, 6 }; + yield return new object[] { "0.123abc", NumberStyles.Number | NumberStyles.AllowTrailingInvalidCharacters, null, 0.123m, 5 }; + + // With leading whitespace + yield return new object[] { " 123.45abc", NumberStyles.Number | NumberStyles.AllowTrailingInvalidCharacters, null, 123.45m, 8 }; + + // With signs + yield return new object[] { "+123.45abc", NumberStyles.Number | NumberStyles.AllowTrailingInvalidCharacters, null, 123.45m, 7 }; + yield return new object[] { "-456.78xyz", NumberStyles.Number | NumberStyles.AllowTrailingInvalidCharacters, null, -456.78m, 7 }; + + // Integer without decimal point + yield return new object[] { "123abc", NumberStyles.Number | NumberStyles.AllowTrailingInvalidCharacters, null, 123m, 3 }; + + // With thousands separator + NumberFormatInfo customFormat = new NumberFormatInfo() { NumberGroupSeparator = "," }; + yield return new object[] { "1,234abc", NumberStyles.Number | NumberStyles.AllowTrailingInvalidCharacters, customFormat, 1234m, 5 }; + + // Max and min values with trailing characters + yield return new object[] { "79228162514264337593543950335abc", NumberStyles.Number | NumberStyles.AllowTrailingInvalidCharacters, null, 79228162514264337593543950335m, 29 }; + yield return new object[] { "-79228162514264337593543950335xyz", NumberStyles.Number | NumberStyles.AllowTrailingInvalidCharacters, null, -79228162514264337593543950335m, 30 }; + + // Valid number without trailing characters + yield return new object[] { "123.45", NumberStyles.Number | NumberStyles.AllowTrailingInvalidCharacters, null, 123.45m, 6 }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_TestData))] + public static void Parse_AllowTrailingInvalidCharacters(string value, NumberStyles style, IFormatProvider provider, decimal expectedValue, int expectedCharsConsumed) + { + decimal result; + int charsConsumed; + + // Test string overload with charsConsumed + Assert.True(decimal.TryParse(value, style, provider, out result, out charsConsumed)); + Assert.Equal(expectedValue, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + // Test ReadOnlySpan overload with charsConsumed + Assert.True(decimal.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(expectedValue, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + // Test UTF-8 overload with bytesConsumed + byte[] utf8Bytes = Encoding.UTF8.GetBytes(value); + int bytesConsumed; + Assert.True(decimal.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out bytesConsumed)); + Assert.Equal(expectedValue, result); + // For ASCII characters, bytes consumed should equal chars consumed + if (value.All(c => c < 128)) + { + Assert.Equal(expectedCharsConsumed, bytesConsumed); + } + } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_Invalid_TestData() + { + // Empty string + yield return new object[] { "", NumberStyles.Number | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Only invalid characters (no valid number) + yield return new object[] { "abc", NumberStyles.Number | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Overflow + yield return new object[] { "79228162514264337593543950336abc", NumberStyles.Number | NumberStyles.AllowTrailingInvalidCharacters, null }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_Invalid_TestData))] + public static void Parse_AllowTrailingInvalidCharacters_Invalid(string value, NumberStyles style, IFormatProvider provider) + { + decimal result; + int charsConsumed; + + // Test string overload with charsConsumed + Assert.False(decimal.TryParse(value, style, provider, out result, out charsConsumed)); + Assert.Equal(0m, result); + Assert.Equal(0, charsConsumed); + + // Test ReadOnlySpan overload with charsConsumed + Assert.False(decimal.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(0m, result); + Assert.Equal(0, charsConsumed); + } } } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DoubleTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DoubleTests.cs index 946ec2c12cda41..1d2ff030200d6f 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DoubleTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DoubleTests.cs @@ -1856,5 +1856,120 @@ public static void RadiansToDegreesTest(double value, double expectedResult, dou AssertExtensions.Equal(-expectedResult, double.RadiansToDegrees(-value), allowedVariance); AssertExtensions.Equal(+expectedResult, double.RadiansToDegrees(+value), allowedVariance); } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestData() + { + // Basic floating point parsing with trailing invalid characters + yield return new object[] { "123.45abc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 123.45, 6 }; + yield return new object[] { "456.78xyz", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 456.78, 6 }; + yield return new object[] { "0.123abc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 0.123, 5 }; + + // With leading whitespace + yield return new object[] { " 123.45abc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 123.45, 8 }; + + // With signs + yield return new object[] { "+123.45abc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 123.45, 7 }; + yield return new object[] { "-456.78xyz", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, -456.78, 7 }; + + // With exponent + yield return new object[] { "1.23e10abc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 1.23e10, 7 }; + yield return new object[] { "4.56E-5xyz", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 4.56E-5, 7 }; + yield return new object[] { "1E10abc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 1E10, 4 }; + + // Integer without decimal point + yield return new object[] { "123abc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 123.0, 3 }; + yield return new object[] { "456xyz", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 456.0, 3 }; + + // Special values + yield return new object[] { "Infinityabc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, CultureInfo.InvariantCulture, double.PositiveInfinity, 8 }; + yield return new object[] { "-Infinityxyz", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, CultureInfo.InvariantCulture, double.NegativeInfinity, 9 }; + yield return new object[] { "NaNabc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, CultureInfo.InvariantCulture, double.NaN, 3 }; + + // Valid number without trailing characters + yield return new object[] { "123.45", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 123.45, 6 }; + + // Stop at invalid exponent + yield return new object[] { "123eabc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 123.0, 3 }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_TestData))] + public static void Parse_AllowTrailingInvalidCharacters(string value, NumberStyles style, IFormatProvider provider, double expectedValue, int expectedCharsConsumed) + { + double result; + int charsConsumed; + + // Test string overload with charsConsumed + Assert.True(double.TryParse(value, style, provider, out result, out charsConsumed)); + if (double.IsNaN(expectedValue)) + { + Assert.True(double.IsNaN(result)); + } + else + { + Assert.Equal(expectedValue, result); + } + Assert.Equal(expectedCharsConsumed, charsConsumed); + + // Test ReadOnlySpan overload with charsConsumed + Assert.True(double.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + if (double.IsNaN(expectedValue)) + { + Assert.True(double.IsNaN(result)); + } + else + { + Assert.Equal(expectedValue, result); + } + Assert.Equal(expectedCharsConsumed, charsConsumed); + + // Test UTF-8 overload with bytesConsumed + byte[] utf8Bytes = Encoding.UTF8.GetBytes(value); + int bytesConsumed; + Assert.True(double.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out bytesConsumed)); + if (double.IsNaN(expectedValue)) + { + Assert.True(double.IsNaN(result)); + } + else + { + Assert.Equal(expectedValue, result); + } + // For ASCII characters, bytes consumed should equal chars consumed + if (value.All(c => c < 128)) + { + Assert.Equal(expectedCharsConsumed, bytesConsumed); + } + } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_Invalid_TestData() + { + // Empty string + yield return new object[] { "", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Only invalid characters (no valid number) + yield return new object[] { "abc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Invalid start characters + yield return new object[] { ".abc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_Invalid_TestData))] + public static void Parse_AllowTrailingInvalidCharacters_Invalid(string value, NumberStyles style, IFormatProvider provider) + { + double result; + int charsConsumed; + + // Test string overload with charsConsumed + Assert.False(double.TryParse(value, style, provider, out result, out charsConsumed)); + Assert.Equal(0.0, result); + Assert.Equal(0, charsConsumed); + + // Test ReadOnlySpan overload with charsConsumed + Assert.False(double.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(0.0, result); + Assert.Equal(0, charsConsumed); + } } } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/HalfTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/HalfTests.cs index 3fd4be9a7c951b..ce6514ee64f671 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/HalfTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/HalfTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Dynamic; using System.Globalization; +using System.Linq; using System.Text; using Xunit; @@ -2450,5 +2451,108 @@ public static void ILogB_Subnormal() Half subnormal = BitConverter.UInt16BitsToHalf(0x0200); Assert.Equal(-15, Half.ILogB(subnormal)); } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestData() + { + // Basic Half parsing with trailing invalid characters + yield return new object[] { "123.45abc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, (Half)123.45, 6 }; + yield return new object[] { "1.5xyz", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, (Half)1.5, 3 }; + yield return new object[] { "0.123abc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, (Half)0.123, 5 }; + + // With leading whitespace + yield return new object[] { " 12.5abc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, (Half)12.5, 6 }; + + // With signs + yield return new object[] { "+12.5abc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, (Half)12.5, 5 }; + yield return new object[] { "-45.5xyz", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, (Half)(-45.5), 5 }; + + // With exponent + yield return new object[] { "1.5e2abc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, (Half)1.5e2, 5 }; + + // Special values + yield return new object[] { "Infinityabc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, CultureInfo.InvariantCulture, Half.PositiveInfinity, 8 }; + yield return new object[] { "-Infinityxyz", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, CultureInfo.InvariantCulture, Half.NegativeInfinity, 9 }; + yield return new object[] { "NaNabc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, CultureInfo.InvariantCulture, Half.NaN, 3 }; + + // Valid number without trailing characters + yield return new object[] { "123.45", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, (Half)123.45, 6 }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_TestData))] + public static void Parse_AllowTrailingInvalidCharacters(string value, NumberStyles style, IFormatProvider provider, Half expectedValue, int expectedCharsConsumed) + { + Half result; + int charsConsumed; + + // Test string overload with charsConsumed + Assert.True(Half.TryParse(value, style, provider, out result, out charsConsumed)); + if (Half.IsNaN(expectedValue)) + { + Assert.True(Half.IsNaN(result)); + } + else + { + Assert.Equal(expectedValue, result); + } + Assert.Equal(expectedCharsConsumed, charsConsumed); + + // Test ReadOnlySpan overload with charsConsumed + Assert.True(Half.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + if (Half.IsNaN(expectedValue)) + { + Assert.True(Half.IsNaN(result)); + } + else + { + Assert.Equal(expectedValue, result); + } + Assert.Equal(expectedCharsConsumed, charsConsumed); + + // Test UTF-8 overload with bytesConsumed + byte[] utf8Bytes = Encoding.UTF8.GetBytes(value); + int bytesConsumed; + Assert.True(Half.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out bytesConsumed)); + if (Half.IsNaN(expectedValue)) + { + Assert.True(Half.IsNaN(result)); + } + else + { + Assert.Equal(expectedValue, result); + } + // For ASCII characters, bytes consumed should equal chars consumed + if (value.All(c => c < 128)) + { + Assert.Equal(expectedCharsConsumed, bytesConsumed); + } + } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_Invalid_TestData() + { + // Empty string + yield return new object[] { "", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Only invalid characters (no valid number) + yield return new object[] { "abc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_Invalid_TestData))] + public static void Parse_AllowTrailingInvalidCharacters_Invalid(string value, NumberStyles style, IFormatProvider provider) + { + Half result; + int charsConsumed; + + // Test string overload with charsConsumed + Assert.False(Half.TryParse(value, style, provider, out result, out charsConsumed)); + Assert.Equal((Half)0.0, result); + Assert.Equal(0, charsConsumed); + + // Test ReadOnlySpan overload with charsConsumed + Assert.False(Half.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal((Half)0.0, result); + Assert.Equal(0, charsConsumed); + } } } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Int16Tests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Int16Tests.cs index 4601ac9dad7965..34f81dc6013f0e 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Int16Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Int16Tests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Text; using Xunit; @@ -454,5 +455,80 @@ public static void Parse_Utf8Span_InvalidUtf8() [MemberData(nameof(ToString_TestData))] public static void TryFormat(short i, string format, IFormatProvider provider, string expected) => NumberFormatTestHelper.TryFormatNumberTest(i, format, provider, expected); + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestData() + { + yield return new object[] { "123abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, (short)123, 3 }; + yield return new object[] { "1234xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, (short)1234, 4 }; + yield return new object[] { "-32768abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, (short)-32768, 6 }; + yield return new object[] { "32767xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, (short)32767, 5 }; + yield return new object[] { "ABCxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, (short)0xABC, 3 }; + yield return new object[] { "7FFFxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, (short)0x7FFF, 4 }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_TestData))] + public static void Parse_AllowTrailingInvalidCharacters(string value, NumberStyles style, IFormatProvider provider, short expectedValue, int expectedCharsConsumed) + { + short result; + int charsConsumed; + + Assert.True(short.TryParse(value, style, provider, out result, out charsConsumed)); + Assert.Equal(expectedValue, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + Assert.True(short.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(expectedValue, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + byte[] utf8Bytes = Encoding.UTF8.GetBytes(value); + int bytesConsumed; + Assert.True(short.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out bytesConsumed)); + Assert.Equal(expectedValue, result); + if (value.All(c => c < 128)) + { + Assert.Equal(expectedCharsConsumed, bytesConsumed); + } + } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_Invalid_TestData() + { + yield return new object[] { "", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { " ", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "!!!", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { ".123", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Overflow of the leading valid digits + yield return new object[] { "32768abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "-32769xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "99999abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Invalid hex/binary starting characters + yield return new object[] { "Gxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "2abc", NumberStyles.BinaryNumber | NumberStyles.AllowTrailingInvalidCharacters, null }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_Invalid_TestData))] + public static void Parse_AllowTrailingInvalidCharacters_Invalid(string value, NumberStyles style, IFormatProvider provider) + { + short result; + int charsConsumed; + + Assert.False(short.TryParse(value, style, provider, out result, out charsConsumed)); + Assert.Equal(0, result); + Assert.Equal(0, charsConsumed); + + Assert.False(short.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(0, result); + Assert.Equal(0, charsConsumed); + + byte[] utf8Bytes = Encoding.UTF8.GetBytes(value); + int bytesConsumed; + Assert.False(short.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out bytesConsumed)); + Assert.Equal(0, result); + Assert.Equal(0, bytesConsumed); + } } } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Int32Tests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Int32Tests.cs index d1b9c154aefc97..561b56213169ed 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Int32Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Int32Tests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Text; using Xunit; @@ -988,5 +989,162 @@ public static void TestParseLenientData() Assert.Equal(-9670, int.Parse("-9670", NumberStyles.Integer, nfi)); } } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestData() + { + // Basic integer parsing with trailing invalid characters + yield return new object[] { "123abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 123, 3 }; + yield return new object[] { "456xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 456, 3 }; + yield return new object[] { "789!", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 789, 3 }; + yield return new object[] { "0@#$", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 0, 1 }; + + // With leading whitespace + yield return new object[] { " 123abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 123, 5 }; + yield return new object[] { "\t456xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 456, 4 }; + + // With signs + yield return new object[] { "+123abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 123, 4 }; + yield return new object[] { "-456xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, -456, 4 }; + yield return new object[] { "+0xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 0, 2 }; + yield return new object[] { "-0xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 0, 2 }; + + // HexNumber with trailing invalid characters + yield return new object[] { "ABCxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, 0xABC, 3 }; + yield return new object[] { "FFGHxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, 0xFF, 2 }; + yield return new object[] { "123ZZZ", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, 0x123, 3 }; + yield return new object[] { "0GHI", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, 0x0, 1 }; + yield return new object[] { " ABCxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, 0xABC, 5 }; + + // BinaryNumber with trailing invalid characters + yield return new object[] { "101abc", NumberStyles.BinaryNumber | NumberStyles.AllowTrailingInvalidCharacters, null, 0b101, 3 }; + yield return new object[] { "1112", NumberStyles.BinaryNumber | NumberStyles.AllowTrailingInvalidCharacters, null, 0b111, 3 }; + yield return new object[] { "110xyz", NumberStyles.BinaryNumber | NumberStyles.AllowTrailingInvalidCharacters, null, 0b110, 3 }; + yield return new object[] { "0abc", NumberStyles.BinaryNumber | NumberStyles.AllowTrailingInvalidCharacters, null, 0b0, 1 }; + yield return new object[] { " 101abc", NumberStyles.BinaryNumber | NumberStyles.AllowTrailingInvalidCharacters, null, 0b101, 5 }; + + // Number style with decimal point (should stop at decimal for integers) + yield return new object[] { "123.45", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 123, 3 }; + yield return new object[] { "456.xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 456, 3 }; + + // With thousands separator in invalid position + NumberFormatInfo customFormat = new NumberFormatInfo() { NumberGroupSeparator = "," }; + yield return new object[] { "123,456xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, customFormat, 123, 3 }; + + // Currency style with trailing invalid characters + NumberFormatInfo currencyFormat = new NumberFormatInfo() { CurrencySymbol = "$", CurrencyGroupSeparator = "," }; + yield return new object[] { "$123abc", NumberStyles.Currency | NumberStyles.AllowTrailingInvalidCharacters, currencyFormat, 123, 4 }; + yield return new object[] { "$1,234xyz", NumberStyles.Currency | NumberStyles.AllowTrailingInvalidCharacters, currencyFormat, 1234, 6 }; + yield return new object[] { "($123)abc", NumberStyles.Currency | NumberStyles.AllowTrailingInvalidCharacters, currencyFormat, -123, 6 }; + + // Max and min values with trailing characters + yield return new object[] { "2147483647abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 2147483647, 10 }; + yield return new object[] { "-2147483648xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, -2147483648, 11 }; + + // Edge case: only invalid characters (should fail) + // Edge case: valid number without trailing characters (should still work) + yield return new object[] { "123", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 123, 3 }; + yield return new object[] { "456", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 456, 3 }; + + // Multiple styles combined + yield return new object[] { " +123abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 123, 6 }; + yield return new object[] { " -456xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, -456, 6 }; + + // Trailing whitespace is not considered "invalid" in the traditional sense + // but should be handled when AllowTrailingWhite is not set + yield return new object[] { "123 ", NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingInvalidCharacters, null, 123, 3 }; + yield return new object[] { "123\t", NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingInvalidCharacters, null, 123, 3 }; + + // Stop at null character + yield return new object[] { "123\0abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 123, 4 }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_TestData))] + public static void Parse_AllowTrailingInvalidCharacters(string value, NumberStyles style, IFormatProvider provider, int expectedValue, int expectedCharsConsumed) + { + int result; + int charsConsumed; + + // Test string overload with charsConsumed + Assert.True(int.TryParse(value, style, provider, out result, out charsConsumed)); + Assert.Equal(expectedValue, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + // Test ReadOnlySpan overload with charsConsumed + Assert.True(int.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(expectedValue, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + // Test UTF-8 overload with bytesConsumed + byte[] utf8Bytes = Encoding.UTF8.GetBytes(value); + int bytesConsumed; + Assert.True(int.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out bytesConsumed)); + Assert.Equal(expectedValue, result); + // For ASCII characters, bytes consumed should equal chars consumed + if (value.All(c => c < 128)) + { + Assert.Equal(expectedCharsConsumed, bytesConsumed); + } + } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_Invalid_TestData() + { + // Empty string + yield return new object[] { "", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Only whitespace + yield return new object[] { " ", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Only invalid characters (no valid number) + yield return new object[] { "abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "!!!", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Invalid start characters + yield return new object[] { ".123", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { ",123", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Sign not allowed without AllowLeadingSign + yield return new object[] { "+123abc", NumberStyles.None | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "-123abc", NumberStyles.None | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Overflow + yield return new object[] { "2147483648abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "-2147483649xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "9999999999abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Invalid hex characters at start + yield return new object[] { "Gxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "Zabc", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Invalid binary characters at start + yield return new object[] { "2abc", NumberStyles.BinaryNumber | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "9xyz", NumberStyles.BinaryNumber | NumberStyles.AllowTrailingInvalidCharacters, null }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_Invalid_TestData))] + public static void Parse_AllowTrailingInvalidCharacters_Invalid(string value, NumberStyles style, IFormatProvider provider) + { + int result; + int charsConsumed; + + // Test string overload with charsConsumed + Assert.False(int.TryParse(value, style, provider, out result, out charsConsumed)); + Assert.Equal(0, result); + Assert.Equal(0, charsConsumed); + + // Test ReadOnlySpan overload with charsConsumed + Assert.False(int.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(0, result); + Assert.Equal(0, charsConsumed); + + // Test UTF-8 overload with bytesConsumed + byte[] utf8Bytes = Encoding.UTF8.GetBytes(value); + int bytesConsumed; + Assert.False(int.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out bytesConsumed)); + Assert.Equal(0, result); + Assert.Equal(0, bytesConsumed); + } } } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Int64Tests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Int64Tests.cs index 7e755afd985834..e49f0a880bac0b 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Int64Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Int64Tests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Numerics; using System.Text; using Xunit; @@ -489,5 +490,95 @@ public static void TestNegativeNumberParsingWithHyphen() CultureInfo ci = CultureInfo.GetCultureInfo("sv-SE"); Assert.Equal(-15868, long.Parse("-15868", NumberStyles.Number, ci)); } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestData() + { + // Basic integer parsing with trailing invalid characters + yield return new object[] { "123abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 123L, 3 }; + yield return new object[] { "456xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 456L, 3 }; + yield return new object[] { "789!", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 789L, 3 }; + + // With leading whitespace + yield return new object[] { " 123abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 123L, 5 }; + + // With signs + yield return new object[] { "+123abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 123L, 4 }; + yield return new object[] { "-456xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, -456L, 4 }; + + // HexNumber with trailing invalid characters + yield return new object[] { "ABCxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, 0xABCL, 3 }; + yield return new object[] { "FFGHxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, 0xFFL, 2 }; + + // BinaryNumber with trailing invalid characters + yield return new object[] { "101abc", NumberStyles.BinaryNumber | NumberStyles.AllowTrailingInvalidCharacters, null, 0b101L, 3 }; + yield return new object[] { "1112", NumberStyles.BinaryNumber | NumberStyles.AllowTrailingInvalidCharacters, null, 0b111L, 3 }; + + // Max and min values with trailing characters + yield return new object[] { "9223372036854775807abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 9223372036854775807L, 19 }; + yield return new object[] { "-9223372036854775808xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, -9223372036854775808L, 20 }; + + // Valid number without trailing characters + yield return new object[] { "123", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 123L, 3 }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_TestData))] + public static void Parse_AllowTrailingInvalidCharacters(string value, NumberStyles style, IFormatProvider provider, long expectedValue, int expectedCharsConsumed) + { + long result; + int charsConsumed; + + // Test string overload with charsConsumed + Assert.True(long.TryParse(value, style, provider, out result, out charsConsumed)); + Assert.Equal(expectedValue, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + // Test ReadOnlySpan overload with charsConsumed + Assert.True(long.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(expectedValue, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + // Test UTF-8 overload with bytesConsumed + byte[] utf8Bytes = Encoding.UTF8.GetBytes(value); + int bytesConsumed; + Assert.True(long.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out bytesConsumed)); + Assert.Equal(expectedValue, result); + // For ASCII characters, bytes consumed should equal chars consumed + if (value.All(c => c < 128)) + { + Assert.Equal(expectedCharsConsumed, bytesConsumed); + } + } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_Invalid_TestData() + { + // Empty string + yield return new object[] { "", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Only invalid characters (no valid number) + yield return new object[] { "abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Overflow + yield return new object[] { "9223372036854775808abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "-9223372036854775809xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_Invalid_TestData))] + public static void Parse_AllowTrailingInvalidCharacters_Invalid(string value, NumberStyles style, IFormatProvider provider) + { + long result; + int charsConsumed; + + // Test string overload with charsConsumed + Assert.False(long.TryParse(value, style, provider, out result, out charsConsumed)); + Assert.Equal(0, result); + Assert.Equal(0, charsConsumed); + + // Test ReadOnlySpan overload with charsConsumed + Assert.False(long.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(0, result); + Assert.Equal(0, charsConsumed); + } } } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/SByteTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/SByteTests.cs index effc332bc5b93d..34727d497cfbb6 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/SByteTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/SByteTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Text; using Xunit; @@ -448,5 +449,80 @@ public static void Parse_Utf8Span_InvalidUtf8() [MemberData(nameof(ToString_TestData))] public static void TryFormat(sbyte i, string format, IFormatProvider provider, string expected) => NumberFormatTestHelper.TryFormatNumberTest(i, format, provider, expected); + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestData() + { + yield return new object[] { "123abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, (sbyte)123, 3 }; + yield return new object[] { "12xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, (sbyte)12, 2 }; + yield return new object[] { "-123abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, (sbyte)-123, 4 }; + yield return new object[] { "+127xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, (sbyte)127, 4 }; + yield return new object[] { "-128abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, (sbyte)-128, 4 }; + yield return new object[] { "7Fxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, (sbyte)0x7F, 2 }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_TestData))] + public static void Parse_AllowTrailingInvalidCharacters(string value, NumberStyles style, IFormatProvider provider, sbyte expectedValue, int expectedCharsConsumed) + { + sbyte result; + int charsConsumed; + + Assert.True(sbyte.TryParse(value, style, provider, out result, out charsConsumed)); + Assert.Equal(expectedValue, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + Assert.True(sbyte.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(expectedValue, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + byte[] utf8Bytes = Encoding.UTF8.GetBytes(value); + int bytesConsumed; + Assert.True(sbyte.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out bytesConsumed)); + Assert.Equal(expectedValue, result); + if (value.All(c => c < 128)) + { + Assert.Equal(expectedCharsConsumed, bytesConsumed); + } + } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_Invalid_TestData() + { + yield return new object[] { "", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { " ", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "!!!", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { ".123", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Overflow of the leading valid digits + yield return new object[] { "128abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "-129xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "999abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Invalid hex/binary starting characters + yield return new object[] { "Gxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "2abc", NumberStyles.BinaryNumber | NumberStyles.AllowTrailingInvalidCharacters, null }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_Invalid_TestData))] + public static void Parse_AllowTrailingInvalidCharacters_Invalid(string value, NumberStyles style, IFormatProvider provider) + { + sbyte result; + int charsConsumed; + + Assert.False(sbyte.TryParse(value, style, provider, out result, out charsConsumed)); + Assert.Equal(0, result); + Assert.Equal(0, charsConsumed); + + Assert.False(sbyte.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(0, result); + Assert.Equal(0, charsConsumed); + + byte[] utf8Bytes = Encoding.UTF8.GetBytes(value); + int bytesConsumed; + Assert.False(sbyte.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out bytesConsumed)); + Assert.Equal(0, result); + Assert.Equal(0, bytesConsumed); + } } } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/SingleTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/SingleTests.cs index e15b50c8124bf0..2e765a51fc2f31 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/SingleTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/SingleTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Text; using Xunit; @@ -1650,5 +1651,109 @@ public static void RadiansToDegreesTest(float value, float expectedResult, float AssertExtensions.Equal(-expectedResult, float.RadiansToDegrees(-value), allowedVariance); AssertExtensions.Equal(+expectedResult, float.RadiansToDegrees(+value), allowedVariance); } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestData() + { + // Basic floating point parsing with trailing invalid characters + yield return new object[] { "123.45abc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 123.45f, 6 }; + yield return new object[] { "456.78xyz", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 456.78f, 6 }; + yield return new object[] { "0.123abc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 0.123f, 5 }; + + // With leading whitespace + yield return new object[] { " 123.45abc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 123.45f, 8 }; + + // With signs + yield return new object[] { "+123.45abc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 123.45f, 7 }; + yield return new object[] { "-456.78xyz", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, -456.78f, 7 }; + + // With exponent + yield return new object[] { "1.23e10abc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 1.23e10f, 7 }; + yield return new object[] { "4.56E-5xyz", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 4.56E-5f, 7 }; + + // Special values + yield return new object[] { "Infinityabc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, CultureInfo.InvariantCulture, float.PositiveInfinity, 8 }; + yield return new object[] { "-Infinityxyz", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, CultureInfo.InvariantCulture, float.NegativeInfinity, 9 }; + yield return new object[] { "NaNabc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, CultureInfo.InvariantCulture, float.NaN, 3 }; + + // Valid number without trailing characters + yield return new object[] { "123.45", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 123.45f, 6 }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_TestData))] + public static void Parse_AllowTrailingInvalidCharacters(string value, NumberStyles style, IFormatProvider provider, float expectedValue, int expectedCharsConsumed) + { + float result; + int charsConsumed; + + // Test string overload with charsConsumed + Assert.True(float.TryParse(value, style, provider, out result, out charsConsumed)); + if (float.IsNaN(expectedValue)) + { + Assert.True(float.IsNaN(result)); + } + else + { + Assert.Equal(expectedValue, result); + } + Assert.Equal(expectedCharsConsumed, charsConsumed); + + // Test ReadOnlySpan overload with charsConsumed + Assert.True(float.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + if (float.IsNaN(expectedValue)) + { + Assert.True(float.IsNaN(result)); + } + else + { + Assert.Equal(expectedValue, result); + } + Assert.Equal(expectedCharsConsumed, charsConsumed); + + // Test UTF-8 overload with bytesConsumed + byte[] utf8Bytes = Encoding.UTF8.GetBytes(value); + int bytesConsumed; + Assert.True(float.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out bytesConsumed)); + if (float.IsNaN(expectedValue)) + { + Assert.True(float.IsNaN(result)); + } + else + { + Assert.Equal(expectedValue, result); + } + // For ASCII characters, bytes consumed should equal chars consumed + if (value.All(c => c < 128)) + { + Assert.Equal(expectedCharsConsumed, bytesConsumed); + } + } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_Invalid_TestData() + { + // Empty string + yield return new object[] { "", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Only invalid characters (no valid number) + yield return new object[] { "abc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_Invalid_TestData))] + public static void Parse_AllowTrailingInvalidCharacters_Invalid(string value, NumberStyles style, IFormatProvider provider) + { + float result; + int charsConsumed; + + // Test string overload with charsConsumed + Assert.False(float.TryParse(value, style, provider, out result, out charsConsumed)); + Assert.Equal(0.0f, result); + Assert.Equal(0, charsConsumed); + + // Test ReadOnlySpan overload with charsConsumed + Assert.False(float.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(0.0f, result); + Assert.Equal(0, charsConsumed); + } } } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/UInt16Tests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/UInt16Tests.cs index bc5ffd25435887..a6b6929597ace3 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/UInt16Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/UInt16Tests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Text; using Xunit; @@ -426,5 +427,78 @@ public static void Parse_Utf8Span_InvalidUtf8() [MemberData(nameof(ToString_TestData))] public static void TryFormat(ushort i, string format, IFormatProvider provider, string expected) => NumberFormatTestHelper.TryFormatNumberTest(i, format, provider, expected); + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestData() + { + yield return new object[] { "123abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, (ushort)123, 3 }; + yield return new object[] { "1234xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, (ushort)1234, 4 }; + yield return new object[] { "65535abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, (ushort)65535, 5 }; + yield return new object[] { "ABCxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, (ushort)0xABC, 3 }; + yield return new object[] { "FFFFxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, (ushort)0xFFFF, 4 }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_TestData))] + public static void Parse_AllowTrailingInvalidCharacters(string value, NumberStyles style, IFormatProvider provider, ushort expectedValue, int expectedCharsConsumed) + { + ushort result; + int charsConsumed; + + Assert.True(ushort.TryParse(value, style, provider, out result, out charsConsumed)); + Assert.Equal(expectedValue, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + Assert.True(ushort.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(expectedValue, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + byte[] utf8Bytes = Encoding.UTF8.GetBytes(value); + int bytesConsumed; + Assert.True(ushort.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out bytesConsumed)); + Assert.Equal(expectedValue, result); + if (value.All(c => c < 128)) + { + Assert.Equal(expectedCharsConsumed, bytesConsumed); + } + } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_Invalid_TestData() + { + yield return new object[] { "", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { " ", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "!!!", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { ".123", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Overflow of the leading valid digits + yield return new object[] { "65536abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "999999abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Invalid hex/binary starting characters + yield return new object[] { "Gxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null }; + yield return new object[] { "2abc", NumberStyles.BinaryNumber | NumberStyles.AllowTrailingInvalidCharacters, null }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_Invalid_TestData))] + public static void Parse_AllowTrailingInvalidCharacters_Invalid(string value, NumberStyles style, IFormatProvider provider) + { + ushort result; + int charsConsumed; + + Assert.False(ushort.TryParse(value, style, provider, out result, out charsConsumed)); + Assert.Equal(0, result); + Assert.Equal(0, charsConsumed); + + Assert.False(ushort.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(0, result); + Assert.Equal(0, charsConsumed); + + byte[] utf8Bytes = Encoding.UTF8.GetBytes(value); + int bytesConsumed; + Assert.False(ushort.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out bytesConsumed)); + Assert.Equal(0, result); + Assert.Equal(0, bytesConsumed); + } } } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/UInt32Tests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/UInt32Tests.cs index ade9fe7664bec6..d3f995c96b51bb 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/UInt32Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/UInt32Tests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Numerics; using System.Text; using Xunit; @@ -456,5 +457,89 @@ public static void Parse_Utf8Span_InvalidUtf8() [MemberData(nameof(ToString_TestData))] public static void TryFormat(uint i, string format, IFormatProvider provider, string expected) => NumberFormatTestHelper.TryFormatNumberTest(i, format, provider, expected); + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestData() + { + // Basic unsigned integer parsing with trailing invalid characters + yield return new object[] { "123abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 123u, 3 }; + yield return new object[] { "456xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 456u, 3 }; + yield return new object[] { "0abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 0u, 1 }; + + // With leading whitespace + yield return new object[] { " 123abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 123u, 5 }; + + // HexNumber with trailing invalid characters + yield return new object[] { "ABCxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, 0xABCu, 3 }; + yield return new object[] { "FFGHxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, 0xFFu, 2 }; + + // BinaryNumber with trailing invalid characters + yield return new object[] { "101abc", NumberStyles.BinaryNumber | NumberStyles.AllowTrailingInvalidCharacters, null, 0b101u, 3 }; + yield return new object[] { "1112", NumberStyles.BinaryNumber | NumberStyles.AllowTrailingInvalidCharacters, null, 0b111u, 3 }; + + // Max value with trailing characters + yield return new object[] { "4294967295abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 4294967295u, 10 }; + + // Valid number without trailing characters + yield return new object[] { "123", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 123u, 3 }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_TestData))] + public static void Parse_AllowTrailingInvalidCharacters(string value, NumberStyles style, IFormatProvider provider, uint expectedValue, int expectedCharsConsumed) + { + uint result; + int charsConsumed; + + // Test string overload with charsConsumed + Assert.True(uint.TryParse(value, style, provider, out result, out charsConsumed)); + Assert.Equal(expectedValue, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + // Test ReadOnlySpan overload with charsConsumed + Assert.True(uint.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(expectedValue, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + // Test UTF-8 overload with bytesConsumed + byte[] utf8Bytes = Encoding.UTF8.GetBytes(value); + int bytesConsumed; + Assert.True(uint.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out bytesConsumed)); + Assert.Equal(expectedValue, result); + // For ASCII characters, bytes consumed should equal chars consumed + if (value.All(c => c < 128)) + { + Assert.Equal(expectedCharsConsumed, bytesConsumed); + } + } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_Invalid_TestData() + { + // Empty string + yield return new object[] { "", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Only invalid characters (no valid number) + yield return new object[] { "abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Overflow + yield return new object[] { "4294967296abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_Invalid_TestData))] + public static void Parse_AllowTrailingInvalidCharacters_Invalid(string value, NumberStyles style, IFormatProvider provider) + { + uint result; + int charsConsumed; + + // Test string overload with charsConsumed + Assert.False(uint.TryParse(value, style, provider, out result, out charsConsumed)); + Assert.Equal(0u, result); + Assert.Equal(0, charsConsumed); + + // Test ReadOnlySpan overload with charsConsumed + Assert.False(uint.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(0u, result); + Assert.Equal(0, charsConsumed); + } } } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/UInt64Tests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/UInt64Tests.cs index 2ef2bc24f5a3ef..37c0bbfd22aafb 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/UInt64Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/UInt64Tests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Numerics; using System.Text; using Xunit; @@ -468,5 +469,89 @@ public static void Parse_Utf8Span_InvalidUtf8() [MemberData(nameof(ToString_TestData))] public static void TryFormat(ulong i, string format, IFormatProvider provider, string expected) => NumberFormatTestHelper.TryFormatNumberTest(i, format, provider, expected); + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestData() + { + // Basic unsigned long parsing with trailing invalid characters + yield return new object[] { "123abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 123ul, 3 }; + yield return new object[] { "456xyz", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 456ul, 3 }; + yield return new object[] { "0abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 0ul, 1 }; + + // With leading whitespace + yield return new object[] { " 123abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 123ul, 5 }; + + // HexNumber with trailing invalid characters + yield return new object[] { "ABCxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, 0xABCul, 3 }; + yield return new object[] { "FFGHxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, 0xFFul, 2 }; + + // BinaryNumber with trailing invalid characters + yield return new object[] { "101abc", NumberStyles.BinaryNumber | NumberStyles.AllowTrailingInvalidCharacters, null, 0b101ul, 3 }; + yield return new object[] { "1112", NumberStyles.BinaryNumber | NumberStyles.AllowTrailingInvalidCharacters, null, 0b111ul, 3 }; + + // Max value with trailing characters + yield return new object[] { "18446744073709551615abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 18446744073709551615ul, 20 }; + + // Valid number without trailing characters + yield return new object[] { "123", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 123ul, 3 }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_TestData))] + public static void Parse_AllowTrailingInvalidCharacters(string value, NumberStyles style, IFormatProvider provider, ulong expectedValue, int expectedCharsConsumed) + { + ulong result; + int charsConsumed; + + // Test string overload with charsConsumed + Assert.True(ulong.TryParse(value, style, provider, out result, out charsConsumed)); + Assert.Equal(expectedValue, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + // Test ReadOnlySpan overload with charsConsumed + Assert.True(ulong.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(expectedValue, result); + Assert.Equal(expectedCharsConsumed, charsConsumed); + + // Test UTF-8 overload with bytesConsumed + byte[] utf8Bytes = Encoding.UTF8.GetBytes(value); + int bytesConsumed; + Assert.True(ulong.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out bytesConsumed)); + Assert.Equal(expectedValue, result); + // For ASCII characters, bytes consumed should equal chars consumed + if (value.All(c => c < 128)) + { + Assert.Equal(expectedCharsConsumed, bytesConsumed); + } + } + + public static IEnumerable Parse_AllowTrailingInvalidCharacters_Invalid_TestData() + { + // Empty string + yield return new object[] { "", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Only invalid characters (no valid number) + yield return new object[] { "abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + + // Overflow + yield return new object[] { "18446744073709551616abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null }; + } + + [Theory] + [MemberData(nameof(Parse_AllowTrailingInvalidCharacters_Invalid_TestData))] + public static void Parse_AllowTrailingInvalidCharacters_Invalid(string value, NumberStyles style, IFormatProvider provider) + { + ulong result; + int charsConsumed; + + // Test string overload with charsConsumed + Assert.False(ulong.TryParse(value, style, provider, out result, out charsConsumed)); + Assert.Equal(0ul, result); + Assert.Equal(0, charsConsumed); + + // Test ReadOnlySpan overload with charsConsumed + Assert.False(ulong.TryParse(value.AsSpan(), style, provider, out result, out charsConsumed)); + Assert.Equal(0ul, result); + Assert.Equal(0, charsConsumed); + } } }