Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 29 additions & 19 deletions src/libraries/Common/src/System/Number.Parsing.Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ namespace System
{
internal static partial class Number
{
private static unsafe bool TryParseNumber<TChar>(scoped ref TChar* str, TChar* strEnd, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info)
private static unsafe bool TryParseNumber<TChar>(TChar* str, TChar* strEnd, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info, out int elementsConsumed)
where TChar : unmanaged, IUtfChar<TChar>
{
Debug.Assert(str != null);
Debug.Assert(strEnd != null);
// str/strEnd may be null when the input is an empty span (e.g. default(ReadOnlySpan<TChar>)
// 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);

Expand Down Expand Up @@ -269,33 +271,37 @@ private static unsafe bool TryParseNumber<TChar>(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<TChar>(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.
Comment on lines +281 to +282

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment seems wrong here. Trailing nulls aren't incrementing index/elementsConsumed so they aren't being counted as consumed.


elementsConsumed = index;
return true;
}
}
}
str = p;

elementsConsumed = 0;
return false;
}

internal static unsafe bool TryStringToNumber<TChar>(ReadOnlySpan<TChar> value, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info)
internal static unsafe bool TryStringToNumber<TChar>(ReadOnlySpan<TChar> value, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info, out int elementsConsumed)
where TChar : unmanaged, IUtfChar<TChar>
{
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
Expand Down Expand Up @@ -339,7 +345,11 @@ internal enum ParsingStatus
private static unsafe TChar* MatchChars<TChar>(TChar* p, TChar* pEnd, ReadOnlySpan<TChar> value)
where TChar : unmanaged, IUtfChar<TChar>
{
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))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@
<value>Invalid handle.</value>
</data>
<data name="Arg_InvalidHexBinaryStyle" xml:space="preserve">
<value>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.</value>
<value>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.</value>
</data>
<data name="Arg_InvalidNeutralResourcesLanguage_Asm_Culture" xml:space="preserve">
<value>The NeutralResourcesLanguageAttribute on the assembly "{0}" specifies an invalid culture name: "{1}".</value>
Expand Down Expand Up @@ -4498,4 +4498,4 @@
<data name="InvalidOperation_NoElements" xml:space="preserve">
<value>Sequence contains no elements.</value>
</data>
</root>
</root>
33 changes: 24 additions & 9 deletions src/libraries/System.Private.CoreLib/src/System/Byte.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,19 +124,13 @@ public static byte Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out byte result)
{
NumberFormatInfo.ValidateParseStyleInteger(style);

if (s is null)
{
result = 0;
return false;
}
return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK;
return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK;
}

public static bool TryParse(ReadOnlySpan<char> 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()
Expand Down Expand Up @@ -1104,6 +1098,27 @@ static bool INumberBase<byte>.TryConvertToTruncating<TOther>(byte value, [MaybeN
}
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(string, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out byte result, out int charsConsumed)
{
NumberFormatInfo.ValidateParseStyleInteger(style);
return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK;
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(ReadOnlySpan{char}, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse(ReadOnlySpan<char> 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;
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(ReadOnlySpan{byte}, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse(ReadOnlySpan<byte> 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
//
Expand Down Expand Up @@ -1176,7 +1191,7 @@ public static byte Parse(ReadOnlySpan<byte> utf8Text, NumberStyles style = Numbe
public static bool TryParse(ReadOnlySpan<byte> 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;
}

/// <inheritdoc cref="IUtf8SpanParsable{TSelf}.Parse(ReadOnlySpan{byte}, IFormatProvider?)" />
Expand Down
38 changes: 38 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/Char.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1959,6 +1959,44 @@ static bool INumberBase<char>.TryConvertToTruncating<TOther>(char value, [MaybeN

static bool INumberBase<char>.TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out char result) => TryParse(s, out result);

static bool INumberBase<char>.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<char>.TryParse(ReadOnlySpan<char> 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<char>.TryParse(ReadOnlySpan<byte> 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
//
Expand Down
33 changes: 24 additions & 9 deletions src/libraries/System.Private.CoreLib/src/System/Decimal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -551,19 +551,13 @@ public static decimal Parse(ReadOnlySpan<char> s, NumberStyles style = NumberSty
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out decimal result)
{
NumberFormatInfo.ValidateParseStyleDecimal(style);

if (s == null)
{
result = 0;
return false;
}
return Number.TryParseDecimal(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK;
return Number.TryParseDecimal(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK;
}

public static bool TryParse(ReadOnlySpan<char> 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
Expand Down Expand Up @@ -1781,6 +1775,27 @@ private static bool TryConvertTo<TOther>(decimal value, [MaybeNullWhen(false)] o
}
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(string, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out decimal result, out int charsConsumed)
{
NumberFormatInfo.ValidateParseStyleDecimal(style);
return Number.TryParseDecimal(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK;
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(ReadOnlySpan{char}, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out decimal result, out int charsConsumed)
{
NumberFormatInfo.ValidateParseStyleDecimal(style);
return Number.TryParseDecimal(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK;
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(ReadOnlySpan{byte}, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse(ReadOnlySpan<byte> utf8Text, NumberStyles style, IFormatProvider? provider, out decimal result, out int bytesConsumed)
{
NumberFormatInfo.ValidateParseStyleDecimal(style);
return Number.TryParseDecimal(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed) == Number.ParsingStatus.OK;
}

//
// IParsable
//
Expand Down Expand Up @@ -1820,7 +1835,7 @@ public static decimal Parse(ReadOnlySpan<byte> utf8Text, NumberStyles style = Nu
public static bool TryParse(ReadOnlySpan<byte> 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;
}

/// <inheritdoc cref="IUtf8SpanParsable{TSelf}.Parse(ReadOnlySpan{byte}, IFormatProvider?)" />
Expand Down
33 changes: 24 additions & 9 deletions src/libraries/System.Private.CoreLib/src/System/Double.cs
Original file line number Diff line number Diff line change
Expand Up @@ -427,19 +427,13 @@ public static double Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyl
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out double result)
{
NumberFormatInfo.ValidateParseStyleFloatingPoint(style);

if (s == null)
{
result = 0;
return false;
}
return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result);
return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _);
}

public static bool TryParse(ReadOnlySpan<char> 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 _);
}

//
Expand Down Expand Up @@ -1487,6 +1481,27 @@ private static bool TryConvertTo<TOther>(double value, [MaybeNullWhen(false)] ou
}
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(string, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out double result, out int charsConsumed)
{
NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed);
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(ReadOnlySpan{char}, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse(ReadOnlySpan<char> 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);
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(ReadOnlySpan{byte}, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse(ReadOnlySpan<byte> 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
//
Expand Down Expand Up @@ -2292,7 +2307,7 @@ public static double Parse(ReadOnlySpan<byte> utf8Text, NumberStyles style = Num
public static bool TryParse(ReadOnlySpan<byte> 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 _);
}

/// <inheritdoc cref="IUtf8SpanParsable{TSelf}.Parse(ReadOnlySpan{byte}, IFormatProvider?)" />
Expand Down
2 changes: 1 addition & 1 deletion src/libraries/System.Private.CoreLib/src/System/Enum.cs
Original file line number Diff line number Diff line change
Expand Up @@ -941,7 +941,7 @@ private static bool TryParseByValueOrName<TUnderlying, TStorage>(
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;
Expand Down
Loading
Loading