From 68a36cd9a3ee9198bb4ebe8b2ca724c37d73f8f0 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 17 Dec 2025 19:47:15 -0800 Subject: [PATCH 1/8] Updating the numeric parsing code to support AllowInvalidTrailingCharacters --- .../src/System/Number.Parsing.Common.cs | 36 +-- .../src/Resources/Strings.resx | 4 +- .../System.Private.CoreLib/src/System/Byte.cs | 34 ++- .../System.Private.CoreLib/src/System/Char.cs | 38 +++ .../src/System/Decimal.cs | 34 ++- .../src/System/Double.cs | 34 ++- .../System.Private.CoreLib/src/System/Enum.cs | 2 +- .../System/Globalization/NumberFormatInfo.cs | 10 +- .../src/System/Globalization/NumberStyles.cs | 3 + .../System.Private.CoreLib/src/System/Guid.cs | 2 +- .../System.Private.CoreLib/src/System/Half.cs | 34 ++- .../src/System/Int128.cs | 34 ++- .../src/System/Int16.cs | 34 ++- .../src/System/Int32.cs | 34 ++- .../src/System/Int64.cs | 34 ++- .../src/System/IntPtr.cs | 21 ++ .../src/System/Number.Parsing.cs | 236 ++++++++++-------- .../src/System/Numerics/BFloat16.cs | 34 ++- .../src/System/Numerics/Decimal128.cs | 36 ++- .../src/System/Numerics/Decimal32.cs | 36 ++- .../src/System/Numerics/Decimal64.cs | 36 ++- .../src/System/Numerics/INumberBase.cs | 72 ++++++ .../System/Runtime/InteropServices/NFloat.cs | 21 ++ .../src/System/SByte.cs | 34 ++- .../src/System/Single.cs | 34 ++- .../src/System/UInt128.cs | 34 ++- .../src/System/UInt16.cs | 34 ++- .../src/System/UInt32.cs | 34 ++- .../src/System/UInt64.cs | 34 ++- .../src/System/UIntPtr.cs | 21 ++ .../src/System/Version.cs | 2 +- .../ref/System.Runtime.InteropServices.cs | 3 + .../ref/System.Runtime.Numerics.cs | 6 + .../src/Resources/Strings.resx | 6 +- .../src/System/Number.BigInteger.cs | 83 +++--- .../src/System/Number.Polyfill.cs | 5 +- .../src/System/Numerics/BigInteger.cs | 38 ++- .../src/System/Numerics/Complex.cs | 51 +++- .../System.Runtime/ref/System.Runtime.cs | 67 +++++ 39 files changed, 1117 insertions(+), 228 deletions(-) diff --git a/src/libraries/Common/src/System/Number.Parsing.Common.cs b/src/libraries/Common/src/System/Number.Parsing.Common.cs index cc3bc73c3d1236..9c3ac99fc74881 100644 --- a/src/libraries/Common/src/System/Number.Parsing.Common.cs +++ b/src/libraries/Common/src/System/Number.Parsing.Common.cs @@ -10,7 +10,7 @@ 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); @@ -269,33 +269,37 @@ private static unsafe bool TryParseNumber(scoped ref TChar* str, TChar* s number.IsNegative = false; } } - str = p; - return true; + + int index = (int)(p - str); + int length = (int)(strEnd - str); + + if ((index == length) || ((styles & NumberStyles.AllowTrailingInvalidCharacters) != 0) || TrailingZeros(new ReadOnlySpan(str, length), index)) + { + // For AllowTrailingInvalidCharacters, we want to stop at the first invalid character + // including trailing nulls (zeros). Otherwise, for compatibility we still need to + // process any trailing nulls that exist and report them as having been consumed. + + 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 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..c12071396b548d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Byte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Byte.cs @@ -130,13 +130,13 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I 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 +1104,34 @@ 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); + + if (s is null) + { + result = 0; + charsConsumed = 0; + return false; + } + 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 +1204,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..b133555eb32caa 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Char.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Char.cs @@ -1959,6 +1959,44 @@ 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'; + 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..af7cd0fa973529 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs @@ -557,13 +557,13 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I 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 +1781,34 @@ 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.ValidateParseStyleFloatingPoint(style); + + if (s is null) + { + result = 0; + charsConsumed = 0; + return false; + } + 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.ValidateParseStyleFloatingPoint(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.ValidateParseStyleFloatingPoint(style); + return Number.TryParseDecimal(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed) == Number.ParsingStatus.OK; + } + // // IParsable // @@ -1820,7 +1848,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..21bc3d091114df 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Double.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Double.cs @@ -433,13 +433,13 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I 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 +1487,34 @@ 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); + + if (s is null) + { + result = 0; + charsConsumed = 0; + return false; + } + 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 +2320,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..06a58f6598ea54 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -398,7 +398,7 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I 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 +412,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 +2167,34 @@ 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); + + if (s is null) + { + result = Zero; + charsConsumed = 0; + return false; + } + 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 +2350,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..a6a8a13586be25 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int128.cs @@ -163,13 +163,13 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I 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 +1915,34 @@ 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); + + if (s is null) + { + result = 0; + charsConsumed = 0; + return false; + } + 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 +2138,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..db80ba564e20e9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int16.cs @@ -163,13 +163,13 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I 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 +1294,34 @@ 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); + + if (s is null) + { + result = 0; + charsConsumed = 0; + return false; + } + 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 +1401,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..411dfa82fbdce4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int32.cs @@ -179,13 +179,13 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I 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 +1361,34 @@ 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); + + if (s is null) + { + result = 0; + charsConsumed = 0; + return false; + } + 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 +1468,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..233334f25b9955 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int64.cs @@ -176,13 +176,13 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I 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 +1364,34 @@ 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); + + if (s is null) + { + result = 0; + charsConsumed = 0; + return false; + } + 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 +1471,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..eb8210e6ecabda 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,37 +263,37 @@ 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) { // 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; } @@ -307,7 +307,7 @@ 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 { @@ -545,17 +545,36 @@ internal static ParsingStatus TryParseBinaryIntegerStyle(ReadOn result = isNegative ? -answer : answer; } ParsingStatus status = ParsingStatus.OK; + elementsConsumed = index; Exit: return status; + InvalidExit: + // For AllowTrailingInvalidCharacters, we want to stop at the first invalid character + // including trailing nulls (zeros). Otherwise, for compatibility we still need to + // process any trailing nulls that exist and report them as having been consumed. + + if ((styles & NumberStyles.AllowTrailingInvalidCharacters) != 0) + { + goto DoneAtEndButPotentialOverflow; + } + + if (TrailingZeros(value, index)) + { + index = value.Length; + 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 +584,7 @@ internal static ParsingStatus TryParseBinaryIntegerStyle(ReadOn { if ((styles & NumberStyles.AllowTrailingWhite) == 0) { - goto FalseExit; + goto InvalidExit; } for (index++; index < value.Length; index++) @@ -580,23 +599,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,7 +613,7 @@ 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 bool IsValidChar(uint ch) => HexConverter.IsHexChar((int)ch); @@ -627,7 +633,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 +753,36 @@ 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 +835,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,21 +969,21 @@ 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; } @@ -975,7 +996,7 @@ internal static unsafe ParsingStatus TryParseDecimal(ReadOnlySpan 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,7 +1004,7 @@ 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); ReadOnlySpan positiveInfinitySymbol = info.PositiveInfinitySymbolTChar(); @@ -1116,7 +1137,7 @@ internal static bool SpanEqualsOrdinalIgnoreCase(ReadOnlySpan span } } - private static bool TryParseHexFloatingPoint(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out TFloat result) + 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 +1145,7 @@ private static bool TryParseHexFloatingPoint(ReadOnlySpan if (value.IsEmpty) { + elementsConsumed = 0; return false; } @@ -1140,6 +1162,7 @@ private static bool TryParseHexFloatingPoint(ReadOnlySpan if (index >= value.Length) { + elementsConsumed = 0; return false; } @@ -1170,6 +1193,7 @@ private static bool TryParseHexFloatingPoint(ReadOnlySpan if (index >= value.Length) { + elementsConsumed = 0; return false; } @@ -1178,12 +1202,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 +1304,7 @@ private static bool TryParseHexFloatingPoint(ReadOnlySpan if (!hasIntegerPart && !hasFractionalPart) { + elementsConsumed = 0; return false; } @@ -1291,6 +1318,7 @@ private static bool TryParseHexFloatingPoint(ReadOnlySpan if (index >= value.Length) { + elementsConsumed = 0; return false; } @@ -1314,6 +1342,7 @@ private static bool TryParseHexFloatingPoint(ReadOnlySpan if (index >= value.Length) { + elementsConsumed = 0; return false; } @@ -1340,6 +1369,7 @@ private static bool TryParseHexFloatingPoint(ReadOnlySpan if (index == exponentStart) { + elementsConsumed = 0; return false; } @@ -1351,6 +1381,7 @@ private static bool TryParseHexFloatingPoint(ReadOnlySpan else { // Exponent indicator (p/P) is required + elementsConsumed = 0; return false; } @@ -1364,11 +1395,15 @@ private static bool TryParseHexFloatingPoint(ReadOnlySpan } // For compatibility, allow trailing null characters (same as other number parsers). - if (index != value.Length && !TrailingZeros(value, index)) + if ((index != value.Length) && ((styles & NumberStyles.AllowTrailingInvalidCharacters) == 0) && !TrailingZeros(value, index)) { + 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,99 +1504,104 @@ 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; + } + } + + ReadOnlySpan valueTrim = SpanTrim(value); - if (!TryStringToNumber(value, styles, ref number, info)) + // This code would be simpler if we only had the concept of `InfinitySymbol`, but + // we don't so we'll check the existing cases first and then handle `PositiveSign` + + // `PositiveInfinitySymbol` and `PositiveSign/NegativeSign` + `NaNSymbol` last. + + ReadOnlySpan positiveInfinitySymbol = info.PositiveInfinitySymbolTChar(); + + if (SpanEqualsOrdinalIgnoreCase(valueTrim, positiveInfinitySymbol)) { - ReadOnlySpan valueTrim = SpanTrim(value); + result = TFloat.PositiveInfinity; + return true; + } + + if (SpanEqualsOrdinalIgnoreCase(valueTrim, info.NegativeInfinitySymbolTChar())) + { + result = TFloat.NegativeInfinity; + return true; + } - // This code would be simpler if we only had the concept of `InfinitySymbol`, but - // we don't so we'll check the existing cases first and then handle `PositiveSign` + - // `PositiveInfinitySymbol` and `PositiveSign/NegativeSign` + `NaNSymbol` last. + ReadOnlySpan nanSymbol = info.NaNSymbolTChar(); - ReadOnlySpan positiveInfinitySymbol = info.PositiveInfinitySymbolTChar(); + if (SpanEqualsOrdinalIgnoreCase(valueTrim, nanSymbol)) + { + result = TFloat.NaN; + return true; + } + + var positiveSign = info.PositiveSignTChar(); + + if (SpanStartsWith(valueTrim, positiveSign, StringComparison.OrdinalIgnoreCase)) + { + valueTrim = valueTrim.Slice(positiveSign.Length); if (SpanEqualsOrdinalIgnoreCase(valueTrim, positiveInfinitySymbol)) { result = TFloat.PositiveInfinity; return true; } - - if (SpanEqualsOrdinalIgnoreCase(valueTrim, info.NegativeInfinitySymbolTChar())) + else if (SpanEqualsOrdinalIgnoreCase(valueTrim, nanSymbol)) { - result = TFloat.NegativeInfinity; + result = TFloat.NaN; return true; } - ReadOnlySpan nanSymbol = info.NaNSymbolTChar(); + result = TFloat.Zero; + return false; + } - if (SpanEqualsOrdinalIgnoreCase(valueTrim, nanSymbol)) + ReadOnlySpan negativeSign = info.NegativeSignTChar(); + + if (SpanStartsWith(valueTrim, negativeSign, StringComparison.OrdinalIgnoreCase)) + { + if (SpanEqualsOrdinalIgnoreCase(valueTrim.Slice(negativeSign.Length), nanSymbol)) { result = TFloat.NaN; return true; } - var positiveSign = info.PositiveSignTChar(); - - if (SpanStartsWith(valueTrim, positiveSign, StringComparison.OrdinalIgnoreCase)) - { - valueTrim = valueTrim.Slice(positiveSign.Length); - - if (SpanEqualsOrdinalIgnoreCase(valueTrim, positiveInfinitySymbol)) - { - result = TFloat.PositiveInfinity; - return true; - } - else if (SpanEqualsOrdinalIgnoreCase(valueTrim, nanSymbol)) - { - result = TFloat.NaN; - return true; - } - - result = TFloat.Zero; - return false; - } - - ReadOnlySpan negativeSign = info.NegativeSignTChar(); - - if (SpanStartsWith(valueTrim, negativeSign, StringComparison.OrdinalIgnoreCase)) + if (info.AllowHyphenDuringParsing() && SpanStartsWith(valueTrim, TChar.CastFrom('-')) && SpanEqualsOrdinalIgnoreCase(valueTrim.Slice(1), nanSymbol)) { - if (SpanEqualsOrdinalIgnoreCase(valueTrim.Slice(negativeSign.Length), nanSymbol)) - { - result = TFloat.NaN; - return true; - } - - if (info.AllowHyphenDuringParsing() && SpanStartsWith(valueTrim, TChar.CastFrom('-')) && SpanEqualsOrdinalIgnoreCase(valueTrim.Slice(1), nanSymbol)) - { - result = TFloat.NaN; - return true; - } + result = TFloat.NaN; + return true; } - - result = TFloat.Zero; - return false; // We really failed } - result = NumberToFloat(ref number); - return true; + result = TFloat.Zero; + 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..edce28fe8e09bb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/BFloat16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/BFloat16.cs @@ -314,7 +314,7 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I 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 +328,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 +1941,34 @@ 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); + + if (s is null) + { + result = Zero; + charsConsumed = 0; + return false; + } + 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 +2124,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..fb803c5dccc08b 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; } /// @@ -175,7 +175,7 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I 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 +252,38 @@ 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); + + if (s is null) + { + result = default; + charsConsumed = 0; + return false; + } + 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..b8aab2b05e98a6 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; } /// @@ -179,7 +179,7 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I 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 +258,38 @@ 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); + + if (s is null) + { + result = default; + charsConsumed = 0; + return false; + } + 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..f292424a7a2ac9 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; } /// @@ -186,7 +186,7 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I 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 +259,38 @@ 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); + + if (s is null) + { + result = default; + charsConsumed = 0; + return false; + } + 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..10cba4250e0802 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,30 @@ 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". Types which add support for the new flag will be + // expected to override this method and provide a correct implementation. + + if (TSelf.TryParse(s, style & ~NumberStyles.AllowTrailingInvalidCharacters, 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 +452,30 @@ 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". Types which add support for the new flag will be + // expected to override this method and provide a correct implementation. + + if (TSelf.TryParse(s, style & ~NumberStyles.AllowTrailingInvalidCharacters, 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 +531,30 @@ 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". Types which add support for the new flag will be + // expected to override this method and provide a correct implementation. + + if (TSelf.TryParse(utf8Text, style & ~NumberStyles.AllowTrailingInvalidCharacters, 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..105b39e35e98ac 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SByte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SByte.cs @@ -166,13 +166,13 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I 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 +1273,34 @@ 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); + + if (s is null) + { + result = 0; + charsConsumed = 0; + return false; + } + 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 +1380,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..6807fcb3329542 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Single.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Single.cs @@ -428,13 +428,13 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I 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 +1506,34 @@ 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); + + if (s is null) + { + result = 0; + charsConsumed = 0; + return false; + } + 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 +2236,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..9f74816315ac5a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt128.cs @@ -165,13 +165,13 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I 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 +2031,34 @@ 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); + + if (s is null) + { + result = 0; + charsConsumed = 0; + return false; + } + 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 +2211,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..352aae5ffb2f05 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt16.cs @@ -158,13 +158,13 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I 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 +1109,34 @@ 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); + + if (s is null) + { + result = 0; + charsConsumed = 0; + return false; + } + 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 +1209,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..39a3f202a58f22 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt32.cs @@ -174,13 +174,13 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I 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 +1180,34 @@ 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); + + if (s is null) + { + result = 0; + charsConsumed = 0; + return false; + } + 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 +1280,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..746161746f0dc7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt64.cs @@ -173,13 +173,13 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I 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 +1181,34 @@ 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); + + if (s is null) + { + result = 0; + charsConsumed = 0; + return false; + } + 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 +1281,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..77f261e1258cec 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,10 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle= 0 && int.MinValue < signedLeading && signedLeading <= int.MaxValue) { @@ -253,16 +257,29 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle BigInteger.MaxLength) { result = default; + elementsConsumed = 0; return ParsingStatus.Overflow; } nuint[] bits = new nuint[totalUIntCount]; Span wholeBlockDestination = bits.AsSpan(0, wholeBlockCount); - if (!TParser.TryParseWholeBlocks(value, wholeBlockDestination)) + if (!TParser.TryParseWholeBlocks(value, wholeBlockDestination, out int blocksWritten)) { - goto FailExit; + if ((style & NumberStyles.AllowTrailingInvalidCharacters) != 0) + { + nuint[] actualBits = new nuint[blocksWritten]; + wholeBlockDestination.Slice(0, blocksWritten).CopyTo(actualBits); + + bits = actualBits; + trailingWhiteLength = 0; + } + else + { + goto FailExit; + } } + index += (TParser.DigitsPerBlock * blocksWritten); bits[^1] = leading; @@ -280,6 +297,7 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle BigInteger.MaxLength) { result = default; + elementsConsumed = 0; return ParsingStatus.Overflow; } @@ -288,6 +306,7 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle input, out nuint static virtual bool TryParseSingleBlock(ReadOnlySpan input, out nuint result) => TParser.TryParseUnalignedBlock(input, out result); - static virtual bool TryParseWholeBlocks(ReadOnlySpan input, Span destination) + static virtual bool TryParseWholeBlocks(ReadOnlySpan input, Span destination, out int blocksWritten) { Debug.Assert(destination.Length * TParser.DigitsPerBlock == input.Length); @@ -1557,10 +1578,12 @@ static virtual bool TryParseWholeBlocks(ReadOnlySpan input, Span d int blockStart = input.Length - (i + 1) * TParser.DigitsPerBlock; if (!TParser.TryParseSingleBlock(input.Slice(blockStart, TParser.DigitsPerBlock), out destination[i])) { + blocksWritten = i; return false; } } + blocksWritten = destination.Length; return true; } } 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..fe9ea8b866eb1c 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.Polyfill.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.Polyfill.cs @@ -84,7 +84,7 @@ 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; @@ -93,15 +93,18 @@ internal static bool IsWhiteSpace(this ReadOnlySpan span) { if (DecodeFromUtfChar(span, 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..aaeae01d1b6ef5 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs @@ -2130,13 +2130,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,15 +2163,17 @@ 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; } @@ -2170,6 +2184,7 @@ private static bool TryParse(ReadOnlySpan text, NumberStyles style : !double.TryParse(Unsafe.BitCast, ReadOnlySpan>(slice), style, provider, out real)) { result = default; + elementsConsumed = 0; return false; } @@ -2190,19 +2205,37 @@ private static bool TryParse(ReadOnlySpan text, NumberStyles style : !double.TryParse(Unsafe.BitCast, ReadOnlySpan>(slice), style, 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).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 + trailingWhiteLength; return true; } 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? { From 6f0aa99ef0ed3809ff6b81ad3e7242bebc5dc196 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 10:59:03 -0800 Subject: [PATCH 2/8] Add tests for NumberStyles.AllowTrailingInvalidCharacters (#124054) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Added comprehensive test coverage for the `NumberStyles.AllowTrailingInvalidCharacters` mode across all numeric types. This style allows Parse/TryParse APIs to parse up to the first invalid character and return the number of characters consumed, similar to Utf8Parser behavior. ## Changes **Test Coverage Added:** - All integer types: Int32, Int64, Int16, SByte, UInt32, UInt64, UInt16, Byte - All floating point types: Double, Single, Half, Decimal - Arbitrary precision: BigInteger **Test Scenarios:** - Basic parsing with trailing invalid characters (e.g., `"123abc"` → value: 123, consumed: 3) - Combined with NumberStyles: Integer, HexNumber, BinaryNumber, Currency, Float - Edge cases: max/min values, empty strings, overflow, special values (Infinity, NaN) - All API surface: string, ReadOnlySpan\, ReadOnlySpan\ overloads - Both success and failure paths with proper validation of characters/bytes consumed Tests follow existing patterns in System.Runtime.Tests and mirror the ValidateParser approach used in System.Memory Utf8Parser tests. ## Testing Tests build successfully. Full baseline build required for test execution would exceed time constraints. --- 💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey). --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tannergooding <10487869+tannergooding@users.noreply.github.com> --- .../tests/BigInteger/parse.cs | 86 ++++++++++ .../System.Runtime.Tests/System/ByteTests.cs | 36 ++++ .../System/DecimalTests.cs | 88 ++++++++++ .../System/DoubleTests.cs | 115 +++++++++++++ .../System.Runtime.Tests/System/HalfTests.cs | 104 ++++++++++++ .../System.Runtime.Tests/System/Int16Tests.cs | 36 ++++ .../System.Runtime.Tests/System/Int32Tests.cs | 158 ++++++++++++++++++ .../System.Runtime.Tests/System/Int64Tests.cs | 91 ++++++++++ .../System.Runtime.Tests/System/SByteTests.cs | 36 ++++ .../System/SingleTests.cs | 105 ++++++++++++ .../System/UInt16Tests.cs | 35 ++++ .../System/UInt32Tests.cs | 85 ++++++++++ .../System/UInt64Tests.cs | 85 ++++++++++ 13 files changed, 1060 insertions(+) diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs index 4f682c5216e7e9..c0d998c041afdb 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, new BigInteger(0xFF), 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/tests/System.Runtime.Tests/System/ByteTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ByteTests.cs index d95fd2c3d8666f..f65f7d56b92459 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,40 @@ 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); + } + } } } 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..9557d2bda79c50 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, null, double.PositiveInfinity, 8 }; + yield return new object[] { "-Infinityxyz", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, double.NegativeInfinity, 9 }; + yield return new object[] { "NaNabc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 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..5c197d420de199 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, null, Half.PositiveInfinity, 8 }; + yield return new object[] { "-Infinityxyz", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, Half.NegativeInfinity, 9 }; + yield return new object[] { "NaNabc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 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..45fb635fd859f3 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,40 @@ 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[] { "7FFFabc", 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); + } + } } } 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..d481c3b05c5a5e 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, 3 }; + } + + [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..37863da770149e 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,40 @@ 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); + } + } } } 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..15e3a728379532 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, null, float.PositiveInfinity, 8 }; + yield return new object[] { "-Infinityxyz", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, float.NegativeInfinity, 9 }; + yield return new object[] { "NaNabc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 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..bc0e21dbe5bdad 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,39 @@ 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[] { "FFFFabc", 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); + } + } } } 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); + } } } From 8d565e80916fbe81f5ec4819c89fba9589c5e168 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Sat, 4 Jul 2026 18:37:44 -0700 Subject: [PATCH 3/8] Fix AllowTrailingInvalidCharacters parsing edge cases - Reset elementsConsumed to 0 on the integer and decimal overflow paths so a failed parse never reports a nonzero consumed count. - Restrict floating-point special-value (Infinity/NaN) matching to the non-hex path and honor AllowTrailingInvalidCharacters via prefix matching, reporting leading whitespace plus the matched symbol as consumed. - Apply the same special-value consistency fix to the IEEE-754 decimal path (Decimal32/64/128) and share the matching logic via a helper. - Add AllowTrailingInvalidCharacters to the hex and binary allowed styles. - Remove redundant string null guards from TryParse overloads and rely on ReadOnlySpan null-tolerance; relax internal asserts to tolerate empty spans. - Restore the UTF-8 BOM inadvertently stripped from Double/Single and the IEEE-754 decimal source files. - Add and correct AllowTrailingInvalidCharacters tests, using CultureInfo.InvariantCulture for culture-dependent special values. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.Parsing.Common.cs | 12 +- .../System.Private.CoreLib/src/System/Byte.cs | 13 -- .../src/System/Decimal.cs | 13 -- .../src/System/Double.cs | 13 -- .../System.Private.CoreLib/src/System/Half.cs | 13 -- .../src/System/Int128.cs | 13 -- .../src/System/Int16.cs | 13 -- .../src/System/Int32.cs | 13 -- .../src/System/Int64.cs | 13 -- .../src/System/Number.Parsing.cs | 176 ++++++++++++------ .../src/System/Numerics/BFloat16.cs | 13 -- .../src/System/Numerics/Decimal128.cs | 13 -- .../src/System/Numerics/Decimal32.cs | 13 -- .../src/System/Numerics/Decimal64.cs | 13 -- .../src/System/SByte.cs | 13 -- .../src/System/Single.cs | 13 -- .../src/System/UInt128.cs | 13 -- .../src/System/UInt16.cs | 13 -- .../src/System/UInt32.cs | 13 -- .../src/System/UInt64.cs | 13 -- .../src/System/Number.BigInteger.cs | 52 ++++-- .../src/System/Number.Polyfill.cs | 2 +- .../src/System/Numerics/Complex.cs | 25 +-- .../tests/BigInteger/parse.cs | 2 +- .../tests/ComplexTests.cs | 89 +++++++++ .../System.Runtime.Tests/System/ByteTests.cs | 39 ++++ .../System/Decimal128Tests.cs | 57 ++++++ .../System/Decimal32Tests.cs | 57 ++++++ .../System/Decimal64Tests.cs | 57 ++++++ .../System/DoubleTests.cs | 6 +- .../System.Runtime.Tests/System/HalfTests.cs | 6 +- .../System.Runtime.Tests/System/Int16Tests.cs | 42 ++++- .../System.Runtime.Tests/System/SByteTests.cs | 40 ++++ .../System/SingleTests.cs | 6 +- .../System/UInt16Tests.cs | 41 +++- 35 files changed, 609 insertions(+), 334 deletions(-) diff --git a/src/libraries/Common/src/System/Number.Parsing.Common.cs b/src/libraries/Common/src/System/Number.Parsing.Common.cs index 9c3ac99fc74881..90906c93924880 100644 --- a/src/libraries/Common/src/System/Number.Parsing.Common.cs +++ b/src/libraries/Common/src/System/Number.Parsing.Common.cs @@ -13,8 +13,10 @@ internal static partial class Number 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); @@ -343,7 +345,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/System/Byte.cs b/src/libraries/System.Private.CoreLib/src/System/Byte.cs index c12071396b548d..b835fde027b418 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Byte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Byte.cs @@ -124,12 +124,6 @@ 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, out _) == Number.ParsingStatus.OK; } @@ -1108,13 +1102,6 @@ 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); - - if (s is null) - { - result = 0; - charsConsumed = 0; - return false; - } return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs index af7cd0fa973529..d5a5e08211fb62 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs @@ -551,12 +551,6 @@ 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, out _) == Number.ParsingStatus.OK; } @@ -1785,13 +1779,6 @@ 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.ValidateParseStyleFloatingPoint(style); - - if (s is null) - { - result = 0; - charsConsumed = 0; - return false; - } return Number.TryParseDecimal(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == 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 21bc3d091114df..a305f338d17f3c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Double.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Double.cs @@ -427,12 +427,6 @@ 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, out _); } @@ -1491,13 +1485,6 @@ 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); - - if (s is null) - { - result = 0; - charsConsumed = 0; - return false; - } return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs index 06a58f6598ea54..1b1a8557c8c104 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -392,12 +392,6 @@ 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, out _); } @@ -2171,13 +2165,6 @@ 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); - - if (s is null) - { - result = Zero; - charsConsumed = 0; - return false; - } return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Int128.cs b/src/libraries/System.Private.CoreLib/src/System/Int128.cs index a6a8a13586be25..dfca98a773c392 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int128.cs @@ -157,12 +157,6 @@ 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, out _) == Number.ParsingStatus.OK; } @@ -1919,13 +1913,6 @@ 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); - - if (s is null) - { - result = 0; - charsConsumed = 0; - return false; - } return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == 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 db80ba564e20e9..c24d81e25f9d6a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int16.cs @@ -157,12 +157,6 @@ 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, out _) == Number.ParsingStatus.OK; } @@ -1298,13 +1292,6 @@ 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); - - if (s is null) - { - result = 0; - charsConsumed = 0; - return false; - } return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == 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 411dfa82fbdce4..46a22c321911b8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int32.cs @@ -173,12 +173,6 @@ 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, out _) == Number.ParsingStatus.OK; } @@ -1365,13 +1359,6 @@ 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); - - if (s is null) - { - result = 0; - charsConsumed = 0; - return false; - } return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == 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 233334f25b9955..c3ada9fac1d2ed 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int64.cs @@ -170,12 +170,6 @@ 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, out _) == Number.ParsingStatus.OK; } @@ -1368,13 +1362,6 @@ 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); - - if (s is null) - { - result = 0; - charsConsumed = 0; - return false; - } return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; } 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 eb8210e6ecabda..75ea9169068ca8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -300,6 +300,7 @@ private static ParsingStatus TryParseBinaryIntegerNumber(ReadOn if (!TryNumberBufferToBinaryInteger(ref number, ref result)) { + elementsConsumed = 0; return ParsingStatus.Overflow; } @@ -615,7 +616,7 @@ internal interface IHexOrBinaryParser 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; @@ -625,7 +626,7 @@ internal 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; @@ -990,6 +991,7 @@ internal static unsafe ParsingStatus TryParseDecimal(ReadOnlySpan if (!TryNumberToDecimal(ref number, ref result)) { + elementsConsumed = 0; return ParsingStatus.Overflow; } @@ -1007,46 +1009,62 @@ internal static ParsingStatus TryParseDecimalIeee754(Re 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; } @@ -1054,20 +1072,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; } @@ -1137,6 +1158,30 @@ internal static bool SpanEqualsOrdinalIgnoreCase(ReadOnlySpan span } } + // 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 @@ -1534,73 +1579,96 @@ internal static unsafe bool TryParseFloat(ReadOnlySpan val } } - ReadOnlySpan valueTrim = SpanTrim(value); - - // This code would be simpler if we only had the concept of `InfinitySymbol`, but - // we don't so we'll check the existing cases first and then handle `PositiveSign` + - // `PositiveInfinitySymbol` and `PositiveSign/NegativeSign` + `NaNSymbol` last. - - ReadOnlySpan positiveInfinitySymbol = info.PositiveInfinitySymbolTChar(); - - if (SpanEqualsOrdinalIgnoreCase(valueTrim, positiveInfinitySymbol)) + // 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) { - result = TFloat.PositiveInfinity; - return true; - } - - if (SpanEqualsOrdinalIgnoreCase(valueTrim, info.NegativeInfinitySymbolTChar())) - { - result = TFloat.NegativeInfinity; - return true; - } - - ReadOnlySpan nanSymbol = info.NaNSymbolTChar(); + ReadOnlySpan valueTrim = SpanTrim(value); + bool allowTrailingInvalid = (styles & NumberStyles.AllowTrailingInvalidCharacters) != 0; - if (SpanEqualsOrdinalIgnoreCase(valueTrim, nanSymbol)) - { - result = TFloat.NaN; - return true; - } + // 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; - var positiveSign = info.PositiveSignTChar(); + // This code would be simpler if we only had the concept of `InfinitySymbol`, but + // we don't so we'll check the existing cases first and then handle `PositiveSign` + + // `PositiveInfinitySymbol` and `PositiveSign/NegativeSign` + `NaNSymbol` last. - if (SpanStartsWith(valueTrim, positiveSign, StringComparison.OrdinalIgnoreCase)) - { - valueTrim = valueTrim.Slice(positiveSign.Length); + 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; } - else if (SpanEqualsOrdinalIgnoreCase(valueTrim, nanSymbol)) + + if (TryMatchSpecialValueSymbol(valueTrim, info.NegativeInfinitySymbolTChar(), allowTrailingInvalid, out matchedLength)) { - result = TFloat.NaN; + result = TFloat.NegativeInfinity; + elementsConsumed = allowTrailingInvalid ? leadingOffset + matchedLength : value.Length; return true; } - result = TFloat.Zero; - return false; - } - - ReadOnlySpan negativeSign = info.NegativeSignTChar(); + ReadOnlySpan nanSymbol = info.NaNSymbolTChar(); - if (SpanStartsWith(valueTrim, negativeSign, StringComparison.OrdinalIgnoreCase)) - { - if (SpanEqualsOrdinalIgnoreCase(valueTrim.Slice(negativeSign.Length), nanSymbol)) + if (TryMatchSpecialValueSymbol(valueTrim, nanSymbol, allowTrailingInvalid, out matchedLength)) { result = TFloat.NaN; + elementsConsumed = allowTrailingInvalid ? leadingOffset + matchedLength : value.Length; return true; } - if (info.AllowHyphenDuringParsing() && SpanStartsWith(valueTrim, TChar.CastFrom('-')) && SpanEqualsOrdinalIgnoreCase(valueTrim.Slice(1), nanSymbol)) + ReadOnlySpan positiveSign = info.PositiveSignTChar(); + + if (SpanStartsWith(valueTrim, positiveSign, StringComparison.OrdinalIgnoreCase)) { - result = TFloat.NaN; - return true; + ReadOnlySpan afterSign = valueTrim.Slice(positiveSign.Length); + + if (TryMatchSpecialValueSymbol(afterSign, positiveInfinitySymbol, allowTrailingInvalid, out matchedLength)) + { + result = TFloat.PositiveInfinity; + elementsConsumed = allowTrailingInvalid ? leadingOffset + positiveSign.Length + matchedLength : value.Length; + return true; + } + 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; + } + + ReadOnlySpan negativeSign = info.NegativeSignTChar(); + + if (SpanStartsWith(valueTrim, negativeSign, StringComparison.OrdinalIgnoreCase)) + { + 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('-')) && TryMatchSpecialValueSymbol(valueTrim.Slice(1), nanSymbol, allowTrailingInvalid, out matchedLength)) + { + result = TFloat.NaN; + elementsConsumed = allowTrailingInvalid ? leadingOffset + 1 + matchedLength : value.Length; + return true; + } } } result = TFloat.Zero; + elementsConsumed = 0; return false; // We really failed } 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 edce28fe8e09bb..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,12 +308,6 @@ 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, out _); } @@ -1945,13 +1939,6 @@ 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); - - if (s is null) - { - result = Zero; - charsConsumed = 0; - return false; - } return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed); } 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 fb803c5dccc08b..721d96bcd297f9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs @@ -169,12 +169,6 @@ 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, out _) == Number.ParsingStatus.OK; } @@ -260,13 +254,6 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Decimal128 result, out int charsConsumed) { NumberFormatInfo.ValidateParseStyleDecimal(style); - - if (s is null) - { - result = default; - charsConsumed = 0; - return false; - } return Number.TryParseDecimalIeee754(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; } 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 b8aab2b05e98a6..d5f409cac95f4c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs @@ -173,12 +173,6 @@ 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, out _) == Number.ParsingStatus.OK; } @@ -266,13 +260,6 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Decimal32 result, out int charsConsumed) { NumberFormatInfo.ValidateParseStyleDecimal(style); - - if (s is null) - { - result = default; - charsConsumed = 0; - return false; - } return Number.TryParseDecimalIeee754(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; } 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 f292424a7a2ac9..67f9806814b01b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs @@ -180,12 +180,6 @@ 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, out _) == Number.ParsingStatus.OK; } @@ -267,13 +261,6 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Decimal64 result, out int charsConsumed) { NumberFormatInfo.ValidateParseStyleDecimal(style); - - if (s is null) - { - result = default; - charsConsumed = 0; - return false; - } return Number.TryParseDecimalIeee754(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; } diff --git a/src/libraries/System.Private.CoreLib/src/System/SByte.cs b/src/libraries/System.Private.CoreLib/src/System/SByte.cs index 105b39e35e98ac..be8a5afca401eb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SByte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SByte.cs @@ -160,12 +160,6 @@ 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, out _) == Number.ParsingStatus.OK; } @@ -1277,13 +1271,6 @@ 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); - - if (s is null) - { - result = 0; - charsConsumed = 0; - return false; - } return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == 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 6807fcb3329542..1f11f7f8ecd5fb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Single.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Single.cs @@ -422,12 +422,6 @@ 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, out _); } @@ -1510,13 +1504,6 @@ 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); - - if (s is null) - { - result = 0; - charsConsumed = 0; - return false; - } return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed); } diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt128.cs b/src/libraries/System.Private.CoreLib/src/System/UInt128.cs index 9f74816315ac5a..b66f13fc38bdea 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt128.cs @@ -159,12 +159,6 @@ 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, out _) == Number.ParsingStatus.OK; } @@ -2035,13 +2029,6 @@ 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); - - if (s is null) - { - result = 0; - charsConsumed = 0; - return false; - } return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == 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 352aae5ffb2f05..04d51ece490225 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt16.cs @@ -152,12 +152,6 @@ 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, out _) == Number.ParsingStatus.OK; } @@ -1113,13 +1107,6 @@ 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); - - if (s is null) - { - result = 0; - charsConsumed = 0; - return false; - } return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == 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 39a3f202a58f22..8db7a767ae5b25 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt32.cs @@ -168,12 +168,6 @@ 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, out _) == Number.ParsingStatus.OK; } @@ -1184,13 +1178,6 @@ 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); - - if (s is null) - { - result = 0; - charsConsumed = 0; - return false; - } return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == 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 746161746f0dc7..71dd66c1e300cc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt64.cs @@ -167,12 +167,6 @@ 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, out _) == Number.ParsingStatus.OK; } @@ -1185,13 +1179,6 @@ 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); - - if (s is null) - { - result = 0; - charsConsumed = 0; - return false; - } return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK; } 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 77f261e1258cec..69d33050fd6e23 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs @@ -175,8 +175,29 @@ internal static ParsingStatus TryParseBigIntegerHexOrBinaryNumberStyle wholeBlockDestination = bits.AsSpan(0, wholeBlockCount); - if (!TParser.TryParseWholeBlocks(value, wholeBlockDestination, out int blocksWritten)) + if (!TParser.TryParseWholeBlocks(value, wholeBlockDestination)) { - if ((style & NumberStyles.AllowTrailingInvalidCharacters) != 0) - { - nuint[] actualBits = new nuint[blocksWritten]; - wholeBlockDestination.Slice(0, blocksWritten).CopyTo(actualBits); - - bits = actualBits; - trailingWhiteLength = 0; - } - else - { - goto FailExit; - } + goto FailExit; } - index += (TParser.DigitsPerBlock * blocksWritten); + index += value.Length; bits[^1] = leading; @@ -1551,6 +1561,8 @@ internal interface IBigIntegerHexOrBinaryParser static abstract nuint GetSignBitsIfValid(uint ch); + static abstract bool IsValidDigit(uint ch); + [MethodImpl(MethodImplOptions.AggressiveInlining)] static virtual bool TryParseUnalignedBlock(ReadOnlySpan input, out nuint result) { @@ -1569,7 +1581,7 @@ static virtual bool TryParseUnalignedBlock(ReadOnlySpan input, out nuint static virtual bool TryParseSingleBlock(ReadOnlySpan input, out nuint result) => TParser.TryParseUnalignedBlock(input, out result); - static virtual bool TryParseWholeBlocks(ReadOnlySpan input, Span destination, out int blocksWritten) + static virtual bool TryParseWholeBlocks(ReadOnlySpan input, Span destination) { Debug.Assert(destination.Length * TParser.DigitsPerBlock == input.Length); @@ -1578,12 +1590,10 @@ static virtual bool TryParseWholeBlocks(ReadOnlySpan input, Span d int blockStart = input.Length - (i + 1) * TParser.DigitsPerBlock; if (!TParser.TryParseSingleBlock(input.Slice(blockStart, TParser.DigitsPerBlock), out destination[i])) { - blocksWritten = i; return false; } } - blocksWritten = destination.Length; return true; } } @@ -1598,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) { @@ -1630,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 fe9ea8b866eb1c..cce131765786dc 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Number.Polyfill.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Number.Polyfill.cs @@ -91,7 +91,7 @@ internal static bool IsWhiteSpace(this ReadOnlySpan span, out int 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; 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 aaeae01d1b6ef5..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); @@ -2177,11 +2178,16 @@ private static bool TryParse(ReadOnlySpan text, NumberStyles style 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; @@ -2201,8 +2207,8 @@ 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; @@ -2217,7 +2223,7 @@ private static bool TryParse(ReadOnlySpan text, NumberStyles style if ((style & NumberStyles.AllowTrailingWhite) != 0) { - if (text.Slice(closeBracket).IsWhiteSpace(out trailingWhiteLength)) + if (text.Slice(closeBracket + 1).IsWhiteSpace(out trailingWhiteLength)) { isInvalid = false; } @@ -2235,7 +2241,7 @@ private static bool TryParse(ReadOnlySpan text, NumberStyles style } result = new Complex(real, imaginary); - elementsConsumed = closeBracket + trailingWhiteLength; + elementsConsumed = closeBracket + 1 + trailingWhiteLength; return true; } @@ -2261,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 c0d998c041afdb..13630859e1d006 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs @@ -1424,7 +1424,7 @@ public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestDat // 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, new BigInteger(0xFF), 2 }; + 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 }; 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/tests/System.Runtime.Tests/System/ByteTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ByteTests.cs index f65f7d56b92459..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 @@ -465,5 +465,44 @@ public static void Parse_AllowTrailingInvalidCharacters(string value, NumberStyl 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/DoubleTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DoubleTests.cs index 9557d2bda79c50..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 @@ -1881,9 +1881,9 @@ public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestDat yield return new object[] { "456xyz", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, 456.0, 3 }; // Special values - yield return new object[] { "Infinityabc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, double.PositiveInfinity, 8 }; - yield return new object[] { "-Infinityxyz", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, double.NegativeInfinity, 9 }; - yield return new object[] { "NaNabc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, double.NaN, 3 }; + 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 }; 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 5c197d420de199..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 @@ -2470,9 +2470,9 @@ public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestDat 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, null, Half.PositiveInfinity, 8 }; - yield return new object[] { "-Infinityxyz", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, Half.NegativeInfinity, 9 }; - yield return new object[] { "NaNabc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, Half.NaN, 3 }; + 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 }; 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 45fb635fd859f3..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 @@ -463,7 +463,7 @@ public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestDat 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[] { "7FFFabc", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, (short)0x7FFF, 4 }; + yield return new object[] { "7FFFxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, (short)0x7FFF, 4 }; } [Theory] @@ -490,5 +490,45 @@ public static void Parse_AllowTrailingInvalidCharacters(string value, NumberStyl 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/SByteTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/SByteTests.cs index 37863da770149e..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 @@ -484,5 +484,45 @@ public static void Parse_AllowTrailingInvalidCharacters(string value, NumberStyl 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 15e3a728379532..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 @@ -1671,9 +1671,9 @@ public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestDat 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, null, float.PositiveInfinity, 8 }; - yield return new object[] { "-Infinityxyz", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, float.NegativeInfinity, 9 }; - yield return new object[] { "NaNabc", NumberStyles.Float | NumberStyles.AllowTrailingInvalidCharacters, null, float.NaN, 3 }; + 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 }; 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 bc0e21dbe5bdad..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 @@ -434,7 +434,7 @@ public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestDat 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[] { "FFFFabc", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, (ushort)0xFFFF, 4 }; + yield return new object[] { "FFFFxyz", NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters, null, (ushort)0xFFFF, 4 }; } [Theory] @@ -461,5 +461,44 @@ public static void Parse_AllowTrailingInvalidCharacters(string value, NumberStyl 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); + } } } From 6a084e356d442c36d1fa62cf40c62b792f7e6934 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Sat, 4 Jul 2026 23:10:50 -0700 Subject: [PATCH 4/8] Address PR #130210 review feedback for AllowTrailingInvalidCharacters - Decimal.cs: the new consumed-count TryParse overloads now use ValidateParseStyleDecimal instead of ValidateParseStyleFloatingPoint, matching the rest of decimal parsing (which rejects hex/binary specifiers). - INumberBase.cs: the default consumed-count TryParse implementations no longer strip NumberStyles.AllowTrailingInvalidCharacters before delegating. Per the approved design, the style is passed through unmodified so a type that does not understand the flag throws as expected, rather than silently succeeding with a masked style. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Decimal.cs | 6 +++--- .../src/System/Numerics/INumberBase.cs | 21 +++++++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs index d5a5e08211fb62..91988fc6e483f7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs @@ -1778,21 +1778,21 @@ 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.ValidateParseStyleFloatingPoint(style); + 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.ValidateParseStyleFloatingPoint(style); + 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.ValidateParseStyleFloatingPoint(style); + NumberFormatInfo.ValidateParseStyleDecimal(style); return Number.TryParseDecimal(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed) == Number.ParsingStatus.OK; } 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 10cba4250e0802..8acaa736d26689 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs @@ -430,10 +430,11 @@ protected static abstract bool TryConvertToTruncating(TSelf value, [Mayb 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". Types which add support for the new flag will be - // expected to override this method and provide a correct implementation. + // 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 & ~NumberStyles.AllowTrailingInvalidCharacters, provider, out result)) + if (TSelf.TryParse(s, style, provider, out result)) { charsConsumed = s.Length; return true; @@ -463,10 +464,11 @@ static virtual bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, 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". Types which add support for the new flag will be - // expected to override this method and provide a correct implementation. + // 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 & ~NumberStyles.AllowTrailingInvalidCharacters, provider, out result)) + if (TSelf.TryParse(s, style, provider, out result)) { charsConsumed = s.Length; return true; @@ -542,10 +544,11 @@ static virtual unsafe bool TryParse(ReadOnlySpan utf8Text, NumberStyles st 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". Types which add support for the new flag will be - // expected to override this method and provide a correct implementation. + // 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 & ~NumberStyles.AllowTrailingInvalidCharacters, provider, out result)) + if (TSelf.TryParse(utf8Text, style, provider, out result)) { bytesConsumed = utf8Text.Length; return true; From 50d221990dbffedad7d223fbe459f1d1d8227144 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 6 Jul 2026 08:26:18 -0700 Subject: [PATCH 5/8] Ensure trailing nulls are consumed and counted --- .../src/System/Number.Parsing.Common.cs | 25 +++++++++----- .../src/System/Number.Parsing.cs | 34 ++++++++----------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/src/libraries/Common/src/System/Number.Parsing.Common.cs b/src/libraries/Common/src/System/Number.Parsing.Common.cs index 90906c93924880..ccf7f4df730d52 100644 --- a/src/libraries/Common/src/System/Number.Parsing.Common.cs +++ b/src/libraries/Common/src/System/Number.Parsing.Common.cs @@ -273,14 +273,15 @@ private static unsafe bool TryParseNumber(TChar* str, TChar* strEnd, Numb } int index = (int)(p - str); - int length = (int)(strEnd - str); + var value = new ReadOnlySpan(str, (int)(strEnd - str)); - if ((index == length) || ((styles & NumberStyles.AllowTrailingInvalidCharacters) != 0) || TrailingZeros(new ReadOnlySpan(str, length), index)) - { - // For AllowTrailingInvalidCharacters, we want to stop at the first invalid character - // including trailing nulls (zeros). Otherwise, for compatibility we still need to - // process any trailing nulls that exist and report them as having been consumed. + // 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; } @@ -305,11 +306,17 @@ internal static unsafe bool TryStringToNumber(ReadOnlySpan value, } [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 nullsToConsume = value.Slice(index).IndexOfAnyExcept(TChar.CastFrom('\0')); + + if (nullsToConsume >= 0) + { + index += nullsToConsume; + } + return index; } private static bool IsWhite(uint ch) => (ch == 0x20) || ((ch - 0x09) <= (0x0D - 0x09)); 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 75ea9169068ca8..235344c9f333af 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -552,18 +552,13 @@ internal static ParsingStatus TryParseBinaryIntegerStyle(ReadOn return status; InvalidExit: - // For AllowTrailingInvalidCharacters, we want to stop at the first invalid character - // including trailing nulls (zeros). Otherwise, for compatibility we still need to - // process any trailing nulls that exist and report them as having been consumed. + // For compatibility we still need to process any trailing + // nulls that exist and report them as having been consumed. - if ((styles & NumberStyles.AllowTrailingInvalidCharacters) != 0) - { - goto DoneAtEndButPotentialOverflow; - } + index = ConsumeTrailingNulls(value, index); - if (TrailingZeros(value, index)) + if ((index == value.Length) || ((styles & NumberStyles.AllowTrailingInvalidCharacters) != 0)) { - index = value.Length; goto DoneAtEndButPotentialOverflow; } @@ -760,18 +755,13 @@ internal static ParsingStatus TryParseBinaryIntegerHexOrBinaryNumberStyle(ReadOnlySpan } } - // For compatibility, allow trailing null characters (same as other number parsers). - if ((index != value.Length) && ((styles & NumberStyles.AllowTrailingInvalidCharacters) == 0) && !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; From 6581a822e4f2c4a13d5d1f5719307f1135c9cb08 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 6 Jul 2026 09:17:49 -0700 Subject: [PATCH 6/8] Ensure we handle the case where only trailing nulls remain --- src/libraries/Common/src/System/Number.Parsing.Common.cs | 9 +++------ src/libraries/System.Private.CoreLib/src/System/Char.cs | 1 + 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/libraries/Common/src/System/Number.Parsing.Common.cs b/src/libraries/Common/src/System/Number.Parsing.Common.cs index ccf7f4df730d52..a1618efff1dc31 100644 --- a/src/libraries/Common/src/System/Number.Parsing.Common.cs +++ b/src/libraries/Common/src/System/Number.Parsing.Common.cs @@ -310,13 +310,10 @@ private static int ConsumeTrailingNulls(ReadOnlySpan value, int in where TChar : unmanaged, IUtfChar { // For compatibility, we need to allow trailing nulls at the end of a number string - var nullsToConsume = value.Slice(index).IndexOfAnyExcept(TChar.CastFrom('\0')); + var remainder = value.Slice(index); - if (nullsToConsume >= 0) - { - index += nullsToConsume; - } - return 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)); diff --git a/src/libraries/System.Private.CoreLib/src/System/Char.cs b/src/libraries/System.Private.CoreLib/src/System/Char.cs index b133555eb32caa..2ae543953c2087 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Char.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Char.cs @@ -1990,6 +1990,7 @@ static bool INumberBase.TryParse(ReadOnlySpan utf8Text, NumberStyles !rune.IsBmp) { result = '\0'; + bytesConsumed = 0; return false; } From 27d8f9c796b72a08334ad110f1a60be2908ff1a5 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 6 Jul 2026 10:16:59 -0700 Subject: [PATCH 7/8] Fix the Int32 AllowTrailingInvalidCharacter test --- .../tests/System.Runtime.Tests/System/Int32Tests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d481c3b05c5a5e..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 @@ -1055,7 +1055,7 @@ public static IEnumerable Parse_AllowTrailingInvalidCharacters_TestDat 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, 3 }; + yield return new object[] { "123\0abc", NumberStyles.Integer | NumberStyles.AllowTrailingInvalidCharacters, null, 123, 4 }; } [Theory] From e80f5b218ba1c6650491e3b51de265a1551e419a Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 6 Jul 2026 11:25:34 -0700 Subject: [PATCH 8/8] Ensure the fast path can light up for AllowTrailingInvalidCharacters --- .../System.Private.CoreLib/src/System/Number.Parsing.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 235344c9f333af..c0ffc894594296 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -267,7 +267,7 @@ internal static ParsingStatus TryParseBinaryInteger(ReadOnlySpa 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, out elementsConsumed); @@ -312,7 +312,7 @@ internal static ParsingStatus TryParseBinaryIntegerStyle(ReadOn 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) {