From f1d305bad0efd3914f3b7370878edaf77d86cc9d Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 26 May 2023 13:50:44 -0700 Subject: [PATCH 01/19] Expose IUtf8SpanParsable --- .../System.Private.CoreLib.Shared.projitems | 1 + .../src/System/IUtf8SpanParsable.cs | 28 +++++++++++++++++++ .../System.Runtime/ref/System.Runtime.cs | 5 ++++ 3 files changed, 34 insertions(+) create mode 100644 src/libraries/System.Private.CoreLib/src/System/IUtf8SpanParsable.cs diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 2bd9120537752..66bd8772d1ce2 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -509,6 +509,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/IUtf8SpanParsable.cs b/src/libraries/System.Private.CoreLib/src/System/IUtf8SpanParsable.cs new file mode 100644 index 0000000000000..3aaf39df1236d --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IUtf8SpanParsable.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace System +{ + /// Defines a mechanism for parsing a span of UTF-8 characters to a value. + /// The type that implements this interface. + public interface IUtf8SpanParsable + where TSelf : IUtf8SpanParsable? + { + /// Parses a span of UTF-8 characters into a value. + /// The span of UTF-8 characters to parse. + /// An object that provides culture-specific formatting information about . + /// The result of parsing . + /// is not in the correct format. + /// is not representable by . + static abstract TSelf Parse(ReadOnlySpan utf8Text, IFormatProvider? provider); + + /// Tries to parse a span of UTF-8 characters into a value. + /// The span of UTF-8 characters to parse. + /// An object that provides culture-specific formatting information about . + /// On return, contains the result of successfully parsing or an undefined value on failure. + /// true if was successfully parsed; otherwise, false. + static abstract bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, [MaybeNullWhen(returnValue: false)] out TSelf result); + } +} diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 518892678e02b..28a2187c52c92 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -3942,6 +3942,11 @@ public partial interface IUtf8SpanFormattable { bool TryFormat(System.Span utf8Destination, out int bytesWritten, System.ReadOnlySpan format, System.IFormatProvider? provider); } + public partial interface IUtf8SpanParsable where TSelf : System.IUtf8SpanParsable? + { + static abstract TSelf Parse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider); + static abstract bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TSelf result); + } public partial class Lazy<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T> { public Lazy() { } From 9b03507732ff81384149e93a1d0593a10e5115fa Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 26 May 2023 13:53:26 -0700 Subject: [PATCH 02/19] Have INumberBase implement IUtf8SpanParsable --- .../src/System/Numerics/INumberBase.cs | 43 ++++++++++++++++++- .../System.Runtime/ref/System.Runtime.cs | 6 ++- 2 files changed, 47 insertions(+), 2 deletions(-) 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 0040a887d171b..d2858a237f671 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs @@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.CompilerServices; +using System.Text; namespace System.Numerics { @@ -23,7 +24,8 @@ public interface INumberBase ISpanParsable, ISubtractionOperators, IUnaryPlusOperators, - IUnaryNegationOperators + IUnaryNegationOperators, + IUtf8SpanParsable where TSelf : INumberBase? { /// Gets the value 1 for the type. @@ -274,6 +276,20 @@ static virtual TSelf CreateTruncating(TOther value) /// is not representable by . static abstract TSelf Parse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider); + /// Parses 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 . + /// The result of parsing . + /// is not a supported value. + /// is not in the correct format. + /// is not representable by . + static virtual TSelf Parse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider) + { + string s = Encoding.Unicode.GetString(utf8Text); + return TSelf.Parse(s, style, provider); + } + /// Tries to convert a value to an instance of the current type, throwing an overflow exception for any values that fall outside the representable range of the current type. /// The type of . /// The value which is used to create the instance of . @@ -353,5 +369,30 @@ protected static abstract bool TryConvertToTruncating(TSelf value, [Mayb /// true if was successfully parsed; otherwise, false. /// 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 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. + /// 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) + { + string s = Encoding.Unicode.GetString(utf8Text); + return TSelf.TryParse(s, style, provider, out result); + } + + static TSelf IUtf8SpanParsable.Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) + { + string s = Encoding.Unicode.GetString(utf8Text); + return TSelf.Parse(s, provider); + } + + static bool IUtf8SpanParsable.TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, [MaybeNullWhen(returnValue: false)] out TSelf result) + { + string s = Encoding.Unicode.GetString(utf8Text); + return TSelf.TryParse(s, provider, out result); + } } } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 28a2187c52c92..6ba6eb6b06fb2 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -10700,7 +10700,7 @@ public partial interface IMultiplyOperators where TSelf static virtual TResult operator checked *(TSelf left, TOther right) { throw null; } static abstract TResult operator *(TSelf left, TOther right); } - public partial interface INumberBase : System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, 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.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators where TSelf : System.Numerics.INumberBase? + public partial interface INumberBase : System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, 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.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.IUtf8SpanParsable where TSelf : System.Numerics.INumberBase? { static abstract TSelf One { get; } static abstract int Radix { get; } @@ -10739,8 +10739,11 @@ static virtual TSelf CreateTruncating(TOther value) static abstract TSelf MaxMagnitudeNumber(TSelf x, TSelf y); static abstract TSelf MinMagnitude(TSelf x, TSelf y); static abstract TSelf MinMagnitudeNumber(TSelf x, TSelf y); + static virtual TSelf Parse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider) { throw null; } static abstract TSelf Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider); static abstract TSelf Parse(string s, System.Globalization.NumberStyles style, System.IFormatProvider? provider); + static TSelf System.IUtf8SpanParsable.Parse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider) { throw null; } + static bool System.IUtf8SpanParsable.TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TSelf result) { throw null; } protected static abstract bool TryConvertFromChecked(TOther value, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TSelf result) #nullable disable where TOther : System.Numerics.INumberBase; @@ -10765,6 +10768,7 @@ protected static abstract bool TryConvertToTruncating(TSelf value, [Syst #nullable disable where TOther : System.Numerics.INumberBase; #nullable restore + 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); } From ca675042e3818faba97d3a1fd4d10b331f807e5c Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Sun, 28 May 2023 12:18:03 -0700 Subject: [PATCH 03/19] Deduplicate some floating-point parsing logic --- .../Text/Utf8Parser/Utf8Parser.Float.cs | 4 +- .../src/System/Double.cs | 55 ++- .../System.Private.CoreLib/src/System/Half.cs | 58 ++- .../src/System/Number.Formatting.cs | 6 +- .../Number.NumberToFloatingPointBits.cs | 377 +++--------------- .../src/System/Number.Parsing.cs | 304 ++++---------- .../src/System/Single.cs | 58 ++- 7 files changed, 282 insertions(+), 580 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Float.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Float.cs index 37f85f2553333..2e361ed42dcc0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Float.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Float.cs @@ -33,7 +33,7 @@ public static unsafe bool TryParse(ReadOnlySpan source, out float value, o if (TryParseNormalAsFloatingPoint(source, ref number, out bytesConsumed, standardFormat)) { - value = Number.NumberToSingle(ref number); + value = Number.NumberToFloat(ref number); return true; } @@ -66,7 +66,7 @@ public static unsafe bool TryParse(ReadOnlySpan source, out double value, if (TryParseNormalAsFloatingPoint(source, ref number, out bytesConsumed, standardFormat)) { - value = Number.NumberToDouble(ref number); + value = Number.NumberToFloat(ref number); return true; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Double.cs b/src/libraries/System.Private.CoreLib/src/System/Double.cs index 523ad54c06047..2c2ae1dbf6a11 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Double.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Double.cs @@ -33,7 +33,8 @@ public readonly struct Double IEquatable, IBinaryFloatingPointIeee754, IMinMaxValue, - IUtf8SpanFormattable + IUtf8SpanFormattable, + IBinaryFloatParseAndFormatInfo { private readonly double m_value; // Do not rename (binary serialization) @@ -375,27 +376,27 @@ public bool TryFormat(Span utf8Destination, out int bytesWritten, [StringS public static double Parse(string s) { if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseDouble(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo); + return Number.ParseFloat(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo); } public static double Parse(string s, NumberStyles style) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseDouble(s, style, NumberFormatInfo.CurrentInfo); + return Number.ParseFloat(s, style, NumberFormatInfo.CurrentInfo); } public static double Parse(string s, IFormatProvider? provider) { if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseDouble(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.GetInstance(provider)); + return Number.ParseFloat(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.GetInstance(provider)); } public static double Parse(string s, NumberStyles style, IFormatProvider? provider) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseDouble(s, style, NumberFormatInfo.GetInstance(provider)); + return Number.ParseFloat(s, style, NumberFormatInfo.GetInstance(provider)); } // Parses a double from a String in the given style. If @@ -409,7 +410,7 @@ public static double Parse(string s, NumberStyles style, IFormatProvider? provid public static double Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Float | NumberStyles.AllowThousands, IFormatProvider? provider = null) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - return Number.ParseDouble(s, style, NumberFormatInfo.GetInstance(provider)); + return Number.ParseFloat(s, style, NumberFormatInfo.GetInstance(provider)); } public static bool TryParse([NotNullWhen(true)] string? s, out double result) @@ -449,7 +450,7 @@ public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatPro private static bool TryParse(ReadOnlySpan s, NumberStyles style, NumberFormatInfo info, out double result) { - return Number.TryParseDouble(s, style, info, out result); + return Number.TryParseFloat(s, style, info, out result); } // @@ -2180,6 +2181,46 @@ public static double TanPi(double x) /// static double IUnaryPlusOperators.operator +(double value) => (double)(+value); + // + // IBinaryFloatParseAndFormatInfo + // + + static int IBinaryFloatParseAndFormatInfo.NumberBufferLength => Number.DoubleNumberBufferLength; + + static ulong IBinaryFloatParseAndFormatInfo.ZeroBits => 0; + static ulong IBinaryFloatParseAndFormatInfo.InfinityBits => 0x7FF00000_00000000; + + static ulong IBinaryFloatParseAndFormatInfo.NormalMantissaMask => (1UL << SignificandLength) - 1; + static ulong IBinaryFloatParseAndFormatInfo.DenormalMantissaMask => TrailingSignificandMask; + + static int IBinaryFloatParseAndFormatInfo.MinBinaryExponent => 1 - MaxExponent; + static int IBinaryFloatParseAndFormatInfo.MaxBinaryExponent => MaxExponent; + + static int IBinaryFloatParseAndFormatInfo.MinDecimalExponent => -324; + static int IBinaryFloatParseAndFormatInfo.MaxDecimalExponent => 309; + + static int IBinaryFloatParseAndFormatInfo.ExponentBias => ExponentBias; + static ushort IBinaryFloatParseAndFormatInfo.ExponentBits => 11; + + static int IBinaryFloatParseAndFormatInfo.OverflowDecimalExponent => (MaxExponent + (2 * SignificandLength)) / 3; + static int IBinaryFloatParseAndFormatInfo.InfinityExponent => 0x7FF; + + static ushort IBinaryFloatParseAndFormatInfo.NormalMantissaBits => SignificandLength; + static ushort IBinaryFloatParseAndFormatInfo.DenormalMantissaBits => TrailingSignificandLength; + + static int IBinaryFloatParseAndFormatInfo.MinFastFloatDecimalExponent => -342; + static int IBinaryFloatParseAndFormatInfo.MaxFastFloatDecimalExponent => 308; + + static int IBinaryFloatParseAndFormatInfo.MinExponentRoundToEven => -4; + static int IBinaryFloatParseAndFormatInfo.MaxExponentRoundToEven => 23; + + static int IBinaryFloatParseAndFormatInfo.MaxExponentFastPath => 22; + static ulong IBinaryFloatParseAndFormatInfo.MaxMantissaFastPath => 2UL << TrailingSignificandLength; + + static double IBinaryFloatParseAndFormatInfo.BitsToFloat(ulong bits) => BitConverter.UInt64BitsToDouble(bits); + + static ulong IBinaryFloatParseAndFormatInfo.FloatToBits(double value) => BitConverter.DoubleToUInt64Bits(value); + // // Helpers // diff --git a/src/libraries/System.Private.CoreLib/src/System/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs index b462c88b6e390..93d149cf11687 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -24,7 +24,8 @@ public readonly struct Half IEquatable, IBinaryFloatingPointIeee754, IMinMaxValue, - IUtf8SpanFormattable + IUtf8SpanFormattable, + IBinaryFloatParseAndFormatInfo { private const NumberStyles DefaultParseStyle = NumberStyles.Float | NumberStyles.AllowThousands; @@ -54,6 +55,9 @@ public readonly struct Half internal const ushort MinTrailingSignificand = 0x0000; internal const ushort MaxTrailingSignificand = 0x03FF; + internal const int TrailingSignificandLength = 10; + internal const int SignificandLength = TrailingSignificandLength + 1; + // Constants representing the private bit-representation for various default values private const ushort PositiveZeroBits = 0x0000; @@ -285,7 +289,7 @@ public static bool IsSubnormal(Half value) public static Half Parse(string s) { if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseHalf(s, DefaultParseStyle, NumberFormatInfo.CurrentInfo); + return Number.ParseFloat(s, DefaultParseStyle, NumberFormatInfo.CurrentInfo); } /// @@ -298,7 +302,7 @@ public static Half Parse(string s, NumberStyles style) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseHalf(s, style, NumberFormatInfo.CurrentInfo); + return Number.ParseFloat(s, style, NumberFormatInfo.CurrentInfo); } /// @@ -310,7 +314,7 @@ public static Half Parse(string s, NumberStyles style) public static Half Parse(string s, IFormatProvider? provider) { if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseHalf(s, DefaultParseStyle, NumberFormatInfo.GetInstance(provider)); + return Number.ParseFloat(s, DefaultParseStyle, NumberFormatInfo.GetInstance(provider)); } /// @@ -324,7 +328,7 @@ public static Half Parse(string s, NumberStyles style = DefaultParseStyle, IForm { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseHalf(s, style, NumberFormatInfo.GetInstance(provider)); + return Number.ParseFloat(s, style, NumberFormatInfo.GetInstance(provider)); } /// @@ -337,7 +341,7 @@ public static Half Parse(string s, NumberStyles style = DefaultParseStyle, IForm public static Half Parse(ReadOnlySpan s, NumberStyles style = DefaultParseStyle, IFormatProvider? provider = null) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - return Number.ParseHalf(s, style, NumberFormatInfo.GetInstance(provider)); + return Number.ParseFloat(s, style, NumberFormatInfo.GetInstance(provider)); } /// @@ -399,7 +403,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.TryParseHalf(s, style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseFloat(s, style, NumberFormatInfo.GetInstance(provider), out result); } private static bool AreZero(Half left, Half right) @@ -2015,5 +2019,45 @@ public static (Half SinPi, Half CosPi) SinCosPi(Half x) /// public static Half operator +(Half value) => value; + + // + // IBinaryFloatParseAndFormatInfo + // + + static int IBinaryFloatParseAndFormatInfo.NumberBufferLength => Number.HalfNumberBufferLength; + + static ulong IBinaryFloatParseAndFormatInfo.ZeroBits => 0; + static ulong IBinaryFloatParseAndFormatInfo.InfinityBits => 0x7C00; + + static ulong IBinaryFloatParseAndFormatInfo.NormalMantissaMask => (1UL << SignificandLength) - 1; + static ulong IBinaryFloatParseAndFormatInfo.DenormalMantissaMask => TrailingSignificandMask; + + static int IBinaryFloatParseAndFormatInfo.MinBinaryExponent => 1 - MaxExponent; + static int IBinaryFloatParseAndFormatInfo.MaxBinaryExponent => MaxExponent; + + static int IBinaryFloatParseAndFormatInfo.MinDecimalExponent => -8; + static int IBinaryFloatParseAndFormatInfo.MaxDecimalExponent => 5; + + static int IBinaryFloatParseAndFormatInfo.ExponentBias => ExponentBias; + static ushort IBinaryFloatParseAndFormatInfo.ExponentBits => 5; + + static int IBinaryFloatParseAndFormatInfo.OverflowDecimalExponent => (MaxExponent + (2 * SignificandLength)) / 3; + static int IBinaryFloatParseAndFormatInfo.InfinityExponent => 0x1F; + + static ushort IBinaryFloatParseAndFormatInfo.NormalMantissaBits => SignificandLength; + static ushort IBinaryFloatParseAndFormatInfo.DenormalMantissaBits => TrailingSignificandLength; + + static int IBinaryFloatParseAndFormatInfo.MinFastFloatDecimalExponent => -8; + static int IBinaryFloatParseAndFormatInfo.MaxFastFloatDecimalExponent => 4; + + static int IBinaryFloatParseAndFormatInfo.MinExponentRoundToEven => -21; + static int IBinaryFloatParseAndFormatInfo.MaxExponentRoundToEven => 5; + + static int IBinaryFloatParseAndFormatInfo.MaxExponentFastPath => 4; + static ulong IBinaryFloatParseAndFormatInfo.MaxMantissaFastPath => 2UL << TrailingSignificandLength; + + static Half IBinaryFloatParseAndFormatInfo.BitsToFloat(ulong bits) => BitConverter.UInt16BitsToHalf((ushort)(bits)); + + static ulong IBinaryFloatParseAndFormatInfo.FloatToBits(Half value) => BitConverter.HalfToUInt16Bits(value); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs index ed689b5d9ec77..a67c7f78aa479 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs @@ -637,7 +637,7 @@ private static int GetFloatingPointMaxDigitsAndPrecision(char fmt, ref int preci // because we know we have enough digits to satisfy roundtrippability), we should validate // that the number actually roundtrips back to the original result. - Debug.Assert(((precision != -1) && (precision < DoublePrecision)) || (BitConverter.DoubleToInt64Bits(value) == BitConverter.DoubleToInt64Bits(NumberToDouble(ref number)))); + Debug.Assert(((precision != -1) && (precision < DoublePrecision)) || (BitConverter.DoubleToInt64Bits(value) == BitConverter.DoubleToInt64Bits(NumberToFloat(ref number)))); if (fmt != 0) { @@ -748,7 +748,7 @@ public static bool TryFormatSingle(float value, ReadOnlySpan format // because we know we have enough digits to satisfy roundtrippability), we should validate // that the number actually roundtrips back to the original result. - Debug.Assert(((precision != -1) && (precision < SinglePrecision)) || (BitConverter.SingleToInt32Bits(value) == BitConverter.SingleToInt32Bits(NumberToSingle(ref number)))); + Debug.Assert(((precision != -1) && (precision < SinglePrecision)) || (BitConverter.SingleToInt32Bits(value) == BitConverter.SingleToInt32Bits(NumberToFloat(ref number)))); if (fmt != 0) { @@ -843,7 +843,7 @@ public static string FormatHalf(Half value, string? format, NumberFormatInfo inf // because we know we have enough digits to satisfy roundtrippability), we should validate // that the number actually roundtrips back to the original result. - Debug.Assert(((precision != -1) && (precision < HalfPrecision)) || (BitConverter.HalfToInt16Bits(value) == BitConverter.HalfToInt16Bits(NumberToHalf(ref number)))); + Debug.Assert(((precision != -1) && (precision < HalfPrecision)) || (BitConverter.HalfToInt16Bits(value) == BitConverter.HalfToInt16Bits(NumberToFloat(ref number)))); if (fmt != 0) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs b/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs index 740ec52a78b90..24e809073f0a3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs @@ -10,106 +10,6 @@ namespace System { internal unsafe partial class Number { - public readonly struct FloatingPointInfo - { - public static readonly FloatingPointInfo Double = new FloatingPointInfo( - denormalMantissaBits: 52, - exponentBits: 11, - maxBinaryExponent: 1023, - exponentBias: 1023, - infinityBits: 0x7FF00000_00000000, - minFastFloatDecimalExponent: -342, - maxFastFloatDecimalExponent: 308, - infinityExponent: 0x7FF, - minExponentRoundToEven: -4, - maxExponentRoundToEven: 23, - maxExponentFastPath: 22 - ); - - public static readonly FloatingPointInfo Single = new FloatingPointInfo( - denormalMantissaBits: 23, - exponentBits: 8, - maxBinaryExponent: 127, - exponentBias: 127, - infinityBits: 0x7F800000, - minFastFloatDecimalExponent: -65, - maxFastFloatDecimalExponent: 38, - infinityExponent: 0xFF, - minExponentRoundToEven: -17, - maxExponentRoundToEven: 10, - maxExponentFastPath: 10 - ); - public static readonly FloatingPointInfo Half = new FloatingPointInfo( - denormalMantissaBits: 10, - exponentBits: 5, - maxBinaryExponent: 15, - exponentBias: 15, - infinityBits: 0x7C00, - minFastFloatDecimalExponent: -8, - maxFastFloatDecimalExponent: 4, - infinityExponent: 0x1F, - minExponentRoundToEven: -21, - maxExponentRoundToEven: 5, - maxExponentFastPath: 4 - ); - - public ulong ZeroBits { get; } - public ulong InfinityBits { get; } - - public ulong NormalMantissaMask { get; } - public ulong DenormalMantissaMask { get; } - - public int MinBinaryExponent { get; } - public int MaxBinaryExponent { get; } - - public int ExponentBias { get; } - public int OverflowDecimalExponent { get; } - - public ushort NormalMantissaBits { get; } - public ushort DenormalMantissaBits { get; } - - public int MinFastFloatDecimalExponent { get; } - public int InfinityExponent { get; } - public int MinExponentRoundToEven { get; } - public int MaxExponentRoundToEven { get; } - - public int MaxExponentFastPath { get; } - - public int MaxFastFloatDecimalExponent { get; } - public ulong MaxMantissaFastPath { get => 2UL << DenormalMantissaBits; } - public ushort ExponentBits { get; } - - public FloatingPointInfo(ushort denormalMantissaBits, ushort exponentBits, int maxBinaryExponent, int exponentBias, ulong infinityBits, int minFastFloatDecimalExponent, int maxFastFloatDecimalExponent, int infinityExponent, int minExponentRoundToEven, int maxExponentRoundToEven, int maxExponentFastPath) - { - ExponentBits = exponentBits; - - DenormalMantissaBits = denormalMantissaBits; - NormalMantissaBits = (ushort)(denormalMantissaBits + 1); // we get an extra (hidden) bit for normal mantissas - - OverflowDecimalExponent = (maxBinaryExponent + 2 * NormalMantissaBits) / 3; - ExponentBias = exponentBias; - - MaxBinaryExponent = maxBinaryExponent; - MinBinaryExponent = 1 - maxBinaryExponent; - - DenormalMantissaMask = (1UL << denormalMantissaBits) - 1; - NormalMantissaMask = (1UL << NormalMantissaBits) - 1; - - InfinityBits = infinityBits; - ZeroBits = 0; - - MaxFastFloatDecimalExponent = maxFastFloatDecimalExponent; - MinFastFloatDecimalExponent = minFastFloatDecimalExponent; - - InfinityExponent = infinityExponent; - - MinExponentRoundToEven = minExponentRoundToEven; - MaxExponentRoundToEven = maxExponentRoundToEven; - - MaxExponentFastPath = maxExponentFastPath; - } - } - private static ReadOnlySpan Pow10DoubleTable => new double[] { 1e0, // 10^0 1e1, // 10^1 @@ -814,25 +714,26 @@ private static void AccumulateDecimalDigitsIntoBigInteger(scoped ref NumberBuffe } } - private static ulong AssembleFloatingPointBits(in FloatingPointInfo info, ulong initialMantissa, int initialExponent, bool hasZeroTail) + private static ulong AssembleFloatingPointBits(ulong initialMantissa, int initialExponent, bool hasZeroTail) + where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo { // number of bits by which we must adjust the mantissa to shift it into the // correct position, and compute the resulting base two exponent for the // normalized mantissa: uint initialMantissaBits = BigInteger.CountSignificantBits(initialMantissa); - int normalMantissaShift = info.NormalMantissaBits - (int)(initialMantissaBits); + int normalMantissaShift = TFloat.NormalMantissaBits - (int)(initialMantissaBits); int normalExponent = initialExponent - normalMantissaShift; ulong mantissa = initialMantissa; int exponent = normalExponent; - if (normalExponent > info.MaxBinaryExponent) + if (normalExponent > TFloat.MaxBinaryExponent) { // The exponent is too large to be represented by the floating point // type; report the overflow condition: - return info.InfinityBits; + return TFloat.InfinityBits; } - else if (normalExponent < info.MinBinaryExponent) + else if (normalExponent < TFloat.MinBinaryExponent) { // The exponent is too small to be represented by the floating point // type as a normal value, but it may be representable as a denormal @@ -840,11 +741,11 @@ private static ulong AssembleFloatingPointBits(in FloatingPointInfo info, ulong // mantissa in order to form a denormal number. (The subtraction of // an extra 1 is to account for the hidden bit of the mantissa that // is not available for use when representing a denormal.) - int denormalMantissaShift = normalMantissaShift + normalExponent + info.ExponentBias - 1; + int denormalMantissaShift = normalMantissaShift + normalExponent + TFloat.ExponentBias - 1; // Denormal values have an exponent of zero, so the debiased exponent is // the negation of the exponent bias: - exponent = -info.ExponentBias; + exponent = -TFloat.ExponentBias; if (denormalMantissaShift < 0) { @@ -856,7 +757,7 @@ private static ulong AssembleFloatingPointBits(in FloatingPointInfo info, ulong // If the mantissa is now zero, we have underflowed: if (mantissa == 0) { - return info.ZeroBits; + return TFloat.ZeroBits; } // When we round the mantissa, the result may be so large that the @@ -875,7 +776,7 @@ private static ulong AssembleFloatingPointBits(in FloatingPointInfo info, ulong // // We detect this case here and re-adjust the mantissa and exponent // appropriately, to form a normal number: - if (mantissa > info.DenormalMantissaMask) + if (mantissa > TFloat.DenormalMantissaMask) { // We add one to the denormalMantissaShift to account for the // hidden mantissa bit (we subtracted one to account for this bit @@ -900,16 +801,16 @@ private static ulong AssembleFloatingPointBits(in FloatingPointInfo info, ulong // When we round the mantissa, it may produce a result that is too // large. In this case, we divide the mantissa by two and increment // the exponent (this does not change the value). - if (mantissa > info.NormalMantissaMask) + if (mantissa > TFloat.NormalMantissaMask) { mantissa >>= 1; exponent++; // The increment of the exponent may have generated a value too // large to be represented. In this case, report the overflow: - if (exponent > info.MaxBinaryExponent) + if (exponent > TFloat.MaxBinaryExponent) { - return info.InfinityBits; + return TFloat.InfinityBits; } } } @@ -921,25 +822,26 @@ private static ulong AssembleFloatingPointBits(in FloatingPointInfo info, ulong // Unset the hidden bit in the mantissa and assemble the floating point value // from the computed components: - mantissa &= info.DenormalMantissaMask; + mantissa &= TFloat.DenormalMantissaMask; - Debug.Assert((info.DenormalMantissaMask & (1UL << info.DenormalMantissaBits)) == 0); - ulong shiftedExponent = ((ulong)(exponent + info.ExponentBias)) << info.DenormalMantissaBits; - Debug.Assert((shiftedExponent & info.DenormalMantissaMask) == 0); - Debug.Assert((mantissa & ~info.DenormalMantissaMask) == 0); - Debug.Assert((shiftedExponent & ~(((1UL << info.ExponentBits) - 1) << info.DenormalMantissaBits)) == 0); // exponent fits in its place + Debug.Assert((TFloat.DenormalMantissaMask & (1UL << TFloat.DenormalMantissaBits)) == 0); + ulong shiftedExponent = ((ulong)(exponent + TFloat.ExponentBias)) << TFloat.DenormalMantissaBits; + Debug.Assert((shiftedExponent & TFloat.DenormalMantissaMask) == 0); + Debug.Assert((mantissa & ~TFloat.DenormalMantissaMask) == 0); + Debug.Assert((shiftedExponent & ~(((1UL << TFloat.ExponentBits) - 1) << TFloat.DenormalMantissaBits)) == 0); // exponent fits in its place return shiftedExponent | mantissa; } - private static ulong ConvertBigIntegerToFloatingPointBits(ref BigInteger value, in FloatingPointInfo info, uint integerBitsOfPrecision, bool hasNonZeroFractionalPart) + private static ulong ConvertBigIntegerToFloatingPointBits(ref BigInteger value, uint integerBitsOfPrecision, bool hasNonZeroFractionalPart) + where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo { - int baseExponent = info.DenormalMantissaBits; + int baseExponent = TFloat.DenormalMantissaBits; // When we have 64-bits or less of precision, we can just get the mantissa directly if (integerBitsOfPrecision <= 64) { - return AssembleFloatingPointBits(in info, value.ToUInt64(), baseExponent, !hasNonZeroFractionalPart); + return AssembleFloatingPointBits(value.ToUInt64(), baseExponent, !hasNonZeroFractionalPart); } (uint topBlockIndex, uint topBlockBits) = Math.DivRem(integerBitsOfPrecision, 32); @@ -982,7 +884,7 @@ private static ulong ConvertBigIntegerToFloatingPointBits(ref BigInteger value, hasZeroTail &= (value.GetBlock(i) == 0); } - return AssembleFloatingPointBits(in info, mantissa, exponent, hasZeroTail); + return AssembleFloatingPointBits(mantissa, exponent, hasZeroTail); } // get 32-bit integer from at most 9 digits @@ -1065,9 +967,10 @@ internal static uint ParseEightDigitsUnrolled(byte* chars) return (uint)val; } - private static ulong NumberToDoubleFloatingPointBits(ref NumberBuffer number, in FloatingPointInfo info) + private static ulong NumberToFloatingPointBits(ref NumberBuffer number) + where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo { - Debug.Assert(info.DenormalMantissaBits == 52); + Debug.Assert(TFloat.DenormalMantissaBits <= FloatingPointMaxDenormalMantissaBits); Debug.Assert(number.GetDigitsPointer()[0] != '0'); @@ -1110,7 +1013,7 @@ private static ulong NumberToDoubleFloatingPointBits(ref NumberBuffer number, in // we can rely on it to produce the correct result when both inputs are exact. // This is known as Clinger's fast path - if ((mantissa <= info.MaxMantissaFastPath) && (fastExponent <= info.MaxExponentFastPath)) + if ((mantissa <= TFloat.MaxMantissaFastPath) && (fastExponent <= TFloat.MaxExponentFastPath)) { double mantissa_d = mantissa; double scale = Pow10DoubleTable[fastExponent]; @@ -1124,193 +1027,36 @@ private static ulong NumberToDoubleFloatingPointBits(ref NumberBuffer number, in mantissa_d *= scale; } - return BitConverter.DoubleToUInt64Bits(mantissa_d); + TFloat result = TFloat.CreateSaturating(mantissa_d); + return TFloat.FloatToBits(result); } // Number Parsing at a Gigabyte per Second, Software: Practice and Experience 51(8), 2021 // https://arxiv.org/abs/2101.11408 - (int Exponent, ulong Mantissa) am = ComputeFloat(exponent, mantissa, info); + (int Exponent, ulong Mantissa) am = ComputeFloat(exponent, mantissa); // If we called ComputeFloat and we have an invalid power of 2 (Exponent < 0), // then we need to go the slow way around again. This is very uncommon. if (am.Exponent > 0) { ulong word = am.Mantissa; - word |= (ulong)(uint)(am.Exponent) << info.DenormalMantissaBits; + word |= (ulong)(uint)(am.Exponent) << TFloat.DenormalMantissaBits; return word; } } - return NumberToFloatingPointBitsSlow(ref number, in info, positiveExponent, integerDigitsPresent, fractionalDigitsPresent); - } - - private static ushort NumberToHalfFloatingPointBits(ref NumberBuffer number, in FloatingPointInfo info) - { - Debug.Assert(info.DenormalMantissaBits == 10); - - Debug.Assert(number.GetDigitsPointer()[0] != '0'); - - Debug.Assert(number.Scale <= FloatingPointMaxExponent); - Debug.Assert(number.Scale >= FloatingPointMinExponent); - - Debug.Assert(number.DigitsCount != 0); - - // The input is of the form 0.Mantissa x 10^Exponent, where 'Mantissa' are - // the decimal digits of the mantissa and 'Exponent' is the decimal exponent. - // We decompose the mantissa into two parts: an integer part and a fractional - // part. If the exponent is positive, then the integer part consists of the - // first 'exponent' digits, or all present digits if there are fewer digits. - // If the exponent is zero or negative, then the integer part is empty. In - // either case, the remaining digits form the fractional part of the mantissa. - - uint totalDigits = (uint)(number.DigitsCount); - uint positiveExponent = (uint)(Math.Max(0, number.Scale)); - - uint integerDigitsPresent = Math.Min(positiveExponent, totalDigits); - uint fractionalDigitsPresent = totalDigits - integerDigitsPresent; - - int exponent = (int)(number.Scale - integerDigitsPresent - fractionalDigitsPresent); - int fastExponent = Math.Abs(exponent); - - // Above 19 digits, we rely on slow path - if (totalDigits <= 19) - { - byte* src = number.GetDigitsPointer(); - - // When the number of significant digits is less than or equal to MaxMantissaFastPath and the - // scale is less than or equal to MaxExponentFastPath, we can take some shortcuts and just rely - // on floating-point arithmetic to compute the correct result. This is - // because each floating-point precision values allows us to exactly represent - // different whole integers and certain powers of 10, depending on the underlying - // formats exact range. Additionally, IEEE operations dictate that the result is - // computed to the infinitely precise result and then rounded, which means that - // we can rely on it to produce the correct result when both inputs are exact. - // This is known as Clinger's fast path - - ulong mantissa = DigitsToUInt64(src, (int)(totalDigits)); - - if ((mantissa <= info.MaxMantissaFastPath) && (fastExponent <= info.MaxExponentFastPath)) - { - double mantissa_d = mantissa; - double scale = Pow10DoubleTable[fastExponent]; - - if (fractionalDigitsPresent != 0) - { - mantissa_d /= scale; - } - else - { - mantissa_d *= scale; - } - - return BitConverter.HalfToUInt16Bits((Half)(mantissa_d)); - } - - // Number Parsing at a Gigabyte per Second, Software: Practice and Experience 51(8), 2021 - // https://arxiv.org/abs/2101.11408 - (int Exponent, ulong Mantissa) am = ComputeFloat(exponent, mantissa, info); - - // If we called ComputeFloat and we have an invalid power of 2 (Exponent < 0), - // then we need to go the slow way around again. This is very uncommon. - if (am.Exponent > 0) - { - ulong word = am.Mantissa; - word |= (ulong)(uint)(am.Exponent) << info.DenormalMantissaBits; - return (ushort)word; - } - - } - return (ushort)NumberToFloatingPointBitsSlow(ref number, in info, positiveExponent, integerDigitsPresent, fractionalDigitsPresent); - } - - private static uint NumberToSingleFloatingPointBits(ref NumberBuffer number, in FloatingPointInfo info) - { - Debug.Assert(info.DenormalMantissaBits == 23); - - Debug.Assert(number.GetDigitsPointer()[0] != '0'); - - Debug.Assert(number.Scale <= FloatingPointMaxExponent); - Debug.Assert(number.Scale >= FloatingPointMinExponent); - - Debug.Assert(number.DigitsCount != 0); - - // The input is of the form 0.Mantissa x 10^Exponent, where 'Mantissa' are - // the decimal digits of the mantissa and 'Exponent' is the decimal exponent. - // We decompose the mantissa into two parts: an integer part and a fractional - // part. If the exponent is positive, then the integer part consists of the - // first 'exponent' digits, or all present digits if there are fewer digits. - // If the exponent is zero or negative, then the integer part is empty. In - // either case, the remaining digits form the fractional part of the mantissa. - - uint totalDigits = (uint)(number.DigitsCount); - uint positiveExponent = (uint)(Math.Max(0, number.Scale)); - - uint integerDigitsPresent = Math.Min(positiveExponent, totalDigits); - uint fractionalDigitsPresent = totalDigits - integerDigitsPresent; - - int exponent = (int)(number.Scale - integerDigitsPresent - fractionalDigitsPresent); - int fastExponent = Math.Abs(exponent); - - - // Above 19 digits, we rely on slow path - if (totalDigits <= 19) - { - - byte* src = number.GetDigitsPointer(); - - // When the number of significant digits is less than or equal to MaxMantissaFastPath and the - // scale is less than or equal to MaxExponentFastPath, we can take some shortcuts and just rely - // on floating-point arithmetic to compute the correct result. This is - // because each floating-point precision values allows us to exactly represent - // different whole integers and certain powers of 10, depending on the underlying - // formats exact range. Additionally, IEEE operations dictate that the result is - // computed to the infinitely precise result and then rounded, which means that - // we can rely on it to produce the correct result when both inputs are exact. - // This is known as Clinger's fast path - - ulong mantissa = DigitsToUInt64(src, (int)(totalDigits)); - - if ((mantissa <= info.MaxMantissaFastPath) && (fastExponent <= info.MaxExponentFastPath)) - { - double mantissa_d = mantissa; - double scale = Pow10DoubleTable[fastExponent]; - - if (fractionalDigitsPresent != 0) - { - mantissa_d /= scale; - } - else - { - mantissa_d *= scale; - } - - return BitConverter.SingleToUInt32Bits((float)(mantissa_d)); - } - - // Number Parsing at a Gigabyte per Second, Software: Practice and Experience 51(8), 2021 - // https://arxiv.org/abs/2101.11408 - (int Exponent, ulong Mantissa) am = ComputeFloat(exponent, mantissa, info); - - // If we called ComputeFloat and we have an invalid power of 2 (Exponent < 0), - // then we need to go the slow way around again. This is very uncommon. - if (am.Exponent > 0) - { - ulong word = am.Mantissa; - word |= (ulong)(uint)(am.Exponent) << info.DenormalMantissaBits; - return (uint)word; - } - } - return (uint)NumberToFloatingPointBitsSlow(ref number, in info, positiveExponent, integerDigitsPresent, fractionalDigitsPresent); + return NumberToFloatingPointBitsSlow(ref number, positiveExponent, integerDigitsPresent, fractionalDigitsPresent); } - private static ulong NumberToFloatingPointBitsSlow(ref NumberBuffer number, in FloatingPointInfo info, uint positiveExponent, uint integerDigitsPresent, uint fractionalDigitsPresent) + private static ulong NumberToFloatingPointBitsSlow(ref NumberBuffer number, uint positiveExponent, uint integerDigitsPresent, uint fractionalDigitsPresent) + where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo { // To generate an N bit mantissa we require N + 1 bits of precision. The // extra bit is used to correctly round the mantissa (if there are fewer bits // than this available, then that's totally okay; in that case we use what we // have and we don't need to round). - uint requiredBitsOfPrecision = (uint)(info.NormalMantissaBits + 1); + uint requiredBitsOfPrecision = (uint)(TFloat.NormalMantissaBits + 1); uint totalDigits = (uint)(number.DigitsCount); uint integerDigitsMissing = positiveExponent - integerDigitsPresent; @@ -1326,9 +1072,9 @@ private static ulong NumberToFloatingPointBitsSlow(ref NumberBuffer number, in F if (integerDigitsMissing > 0) { - if (integerDigitsMissing > info.OverflowDecimalExponent) + if (integerDigitsMissing > TFloat.OverflowDecimalExponent) { - return info.InfinityBits; + return TFloat.InfinityBits; } integerValue.MultiplyPow10(integerDigitsMissing); @@ -1342,9 +1088,8 @@ private static ulong NumberToFloatingPointBitsSlow(ref NumberBuffer number, in F if ((integerBitsOfPrecision >= requiredBitsOfPrecision) || (fractionalDigitsPresent == 0)) { - return ConvertBigIntegerToFloatingPointBits( + return ConvertBigIntegerToFloatingPointBits( ref integerValue, - in info, integerBitsOfPrecision, fractionalDigitsPresent != 0 ); @@ -1365,21 +1110,20 @@ private static ulong NumberToFloatingPointBitsSlow(ref NumberBuffer number, in F fractionalDenominatorExponent += (uint)(-number.Scale); } - if ((integerBitsOfPrecision == 0) && (fractionalDenominatorExponent - (int)(totalDigits)) > info.OverflowDecimalExponent) + if ((integerBitsOfPrecision == 0) && (fractionalDenominatorExponent - (int)(totalDigits)) > TFloat.OverflowDecimalExponent) { // If there were any digits in the integer part, it is impossible to // underflow (because the exponent cannot possibly be small enough), // so if we underflow here it is a true underflow and we return zero. - return info.ZeroBits; + return TFloat.ZeroBits; } AccumulateDecimalDigitsIntoBigInteger(ref number, fractionalFirstIndex, fractionalLastIndex, out BigInteger fractionalNumerator); if (fractionalNumerator.IsZero()) { - return ConvertBigIntegerToFloatingPointBits( + return ConvertBigIntegerToFloatingPointBits( ref integerValue, - in info, integerBitsOfPrecision, fractionalDigitsPresent != 0 ); @@ -1427,9 +1171,8 @@ private static ulong NumberToFloatingPointBitsSlow(ref NumberBuffer number, in F // Thus, we need to do the division to correctly round the result. if (fractionalShift > remainingBitsOfPrecisionRequired) { - return ConvertBigIntegerToFloatingPointBits( + return ConvertBigIntegerToFloatingPointBits( ref integerValue, - in info, integerBitsOfPrecision, fractionalDigitsPresent != 0 ); @@ -1483,7 +1226,7 @@ private static ulong NumberToFloatingPointBitsSlow(ref NumberBuffer number, in F // use in rounding. int finalExponent = (integerBitsOfPrecision > 0) ? (int)(integerBitsOfPrecision) - 2 : -(int)(fractionalExponent) - 1; - return AssembleFloatingPointBits(in info, completeMantissa, finalExponent, hasZeroTail); + return AssembleFloatingPointBits(completeMantissa, finalExponent, hasZeroTail); } private static ulong RightShiftWithRounding(ulong value, int shift, bool hasZeroTail) @@ -1525,22 +1268,22 @@ private static bool ShouldRoundUp(bool lsbBit, bool roundBit, bool hasTailBits) /// /// decimal exponent /// decimal significant (mantissa) - /// parameters for calculations for the value's type (double, float, half) /// Tuple : Exponent (power of 2) and adjusted mantissa - internal static (int Exponent, ulong Mantissa) ComputeFloat(long q, ulong w, FloatingPointInfo info) + internal static (int Exponent, ulong Mantissa) ComputeFloat(long q, ulong w) + where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo { int exponent; ulong mantissa = 0; - if ((w == 0) || (q < info.MinFastFloatDecimalExponent)) + if ((w == 0) || (q < TFloat.MinFastFloatDecimalExponent)) { // result should be zero return default; } - if (q > info.MaxFastFloatDecimalExponent) + if (q > TFloat.MaxFastFloatDecimalExponent) { // we want to get infinity: - exponent = info.InfinityExponent; + exponent = TFloat.InfinityExponent; mantissa = 0; return (exponent, mantissa); } @@ -1554,7 +1297,7 @@ internal static (int Exponent, ulong Mantissa) ComputeFloat(long q, ulong w, Flo // 2. We need an extra bit for rounding purposes // 3. We might lose a bit due to the "upperbit" routine (result too small, requiring a shift) - var product = ComputeProductApproximation(info.DenormalMantissaBits + 3, q, w); + var product = ComputeProductApproximation(TFloat.DenormalMantissaBits + 3, q, w); if (product.low == 0xFFFFFFFFFFFFFFFF) { // could guard it further @@ -1574,9 +1317,9 @@ internal static (int Exponent, ulong Mantissa) ComputeFloat(long q, ulong w, Flo // is easily predicted. Which is best is data specific. int upperBit = (int)(product.high >> 63); - mantissa = product.high >> (upperBit + 64 - info.DenormalMantissaBits - 3); + mantissa = product.high >> (upperBit + 64 - TFloat.DenormalMantissaBits - 3); - exponent = (int)(CalculatePower((int)(q)) + upperBit - lz - (-info.MaxBinaryExponent)); + exponent = (int)(CalculatePower((int)(q)) + upperBit - lz - (-TFloat.MaxBinaryExponent)); if (exponent <= 0) { // we have a subnormal? @@ -1602,21 +1345,21 @@ internal static (int Exponent, ulong Mantissa) ComputeFloat(long q, ulong w, Flo // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer // subnormal, but we can only know this after rounding. // So we only declare a subnormal if we are smaller than the threshold. - exponent = (mantissa < (1UL << info.DenormalMantissaBits)) ? 0 : 1; + exponent = (mantissa < (1UL << TFloat.DenormalMantissaBits)) ? 0 : 1; return (exponent, mantissa); } // usually, we round *up*, but if we fall right in between and and we have an // even basis, we need to round down // We are only concerned with the cases where 5**q fits in single 64-bit word. - if ((product.low <= 1) && (q >= info.MinExponentRoundToEven) && (q <= info.MaxExponentRoundToEven) && + if ((product.low <= 1) && (q >= TFloat.MinExponentRoundToEven) && (q <= TFloat.MaxExponentRoundToEven) && ((mantissa & 3) == 1)) { // We may fall between two floats! // To be in-between two floats we need that in doing // answer.mantissa = product.high >> (upperBit + 64 - info.DenormalMantissaBits - 3); // ... we dropped out only zeroes. But if this happened, then we can go back!!! - if ((mantissa << (upperBit + 64 - info.DenormalMantissaBits - 3)) == product.high) + if ((mantissa << (upperBit + 64 - TFloat.DenormalMantissaBits - 3)) == product.high) { // flip it so that we do not round up mantissa &= ~1UL; @@ -1625,18 +1368,18 @@ internal static (int Exponent, ulong Mantissa) ComputeFloat(long q, ulong w, Flo mantissa += (mantissa & 1); // round up mantissa >>= 1; - if (mantissa >= (2UL << info.DenormalMantissaBits)) + if (mantissa >= (2UL << TFloat.DenormalMantissaBits)) { - mantissa = (1UL << info.DenormalMantissaBits); + mantissa = (1UL << TFloat.DenormalMantissaBits); // undo previous addition exponent++; } - mantissa &= ~(1UL << info.DenormalMantissaBits); - if (exponent >= info.InfinityExponent) + mantissa &= ~(1UL << TFloat.DenormalMantissaBits); + if (exponent >= TFloat.InfinityExponent) { // infinity - exponent = info.InfinityExponent; + exponent = TFloat.InfinityExponent; mantissa = 0; } return (exponent, mantissa); 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 5756b5d93b0b0..c06f1fb4c6e6b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -45,6 +45,46 @@ internal interface IBinaryIntegerParseAndFormatInfo : IBinaryInteger : IBinaryFloatingPointIeee754, IMinMaxValue + where TSelf : unmanaged, IBinaryFloatParseAndFormatInfo + { + static abstract int NumberBufferLength { get; } + + static abstract ulong ZeroBits { get; } + static abstract ulong InfinityBits { get; } + + static abstract ulong NormalMantissaMask { get; } + static abstract ulong DenormalMantissaMask { get; } + + static abstract int MinBinaryExponent { get; } + static abstract int MaxBinaryExponent { get; } + + static abstract int MinDecimalExponent { get; } + static abstract int MaxDecimalExponent { get; } + + static abstract int ExponentBias { get; } + static abstract ushort ExponentBits { get; } + + static abstract int OverflowDecimalExponent { get; } + static abstract int InfinityExponent { get; } + + static abstract ushort NormalMantissaBits { get; } + static abstract ushort DenormalMantissaBits { get; } + + static abstract int MinFastFloatDecimalExponent { get; } + static abstract int MaxFastFloatDecimalExponent { get; } + + static abstract int MinExponentRoundToEven { get; } + static abstract int MaxExponentRoundToEven { get; } + + static abstract int MaxExponentFastPath { get; } + static abstract ulong MaxMantissaFastPath { get; } + + static abstract TSelf BitsToFloat(ulong bits); + + static abstract ulong FloatToBits(TSelf value); + } + internal static partial class Number { private const int Int32Precision = 10; @@ -54,17 +94,10 @@ internal static partial class Number private const int Int128Precision = 39; private const int UInt128Precision = 39; - private const int DoubleMaxExponent = 309; - private const int DoubleMinExponent = -324; + private const int FloatingPointMaxExponent = 309; + private const int FloatingPointMinExponent = -324; - private const int FloatingPointMaxExponent = DoubleMaxExponent; - private const int FloatingPointMinExponent = DoubleMinExponent; - - private const int SingleMaxExponent = 39; - private const int SingleMinExponent = -45; - - private const int HalfMaxExponent = 5; - private const int HalfMinExponent = -8; + private const int FloatingPointMaxDenormalMantissaBits = 52; private static unsafe bool TryNumberBufferToBinaryInteger(ref NumberBuffer number, ref TInteger value) where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo @@ -944,29 +977,10 @@ internal static unsafe bool TryNumberToDecimal(ref NumberBuffer number, ref deci return true; } - internal static double ParseDouble(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) + internal static TFloat ParseFloat(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) + where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo { - if (!TryParseDouble(value, styles, info, out double result)) - { - ThrowOverflowOrFormatException(ParsingStatus.Failed, value); - } - - return result; - } - - internal static float ParseSingle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) - { - if (!TryParseSingle(value, styles, info, out float result)) - { - ThrowOverflowOrFormatException(ParsingStatus.Failed, value); - } - - return result; - } - - internal static Half ParseHalf(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) - { - if (!TryParseHalf(value, styles, info, out Half result)) + if (!TryParseFloat(value, styles, info, out TFloat result)) { ThrowOverflowOrFormatException(ParsingStatus.Failed, value); } @@ -995,9 +1009,10 @@ internal static unsafe ParsingStatus TryParseDecimal(ReadOnlySpan value, N internal static bool SpanStartsWith(ReadOnlySpan span, char c) => !span.IsEmpty && span[0] == c; - internal static unsafe bool TryParseDouble(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out double result) + internal static unsafe bool TryParseFloat(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out TFloat result) + where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo { - NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, stackalloc byte[DoubleNumberBufferLength]); + NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, stackalloc byte[TFloat.NumberBufferLength]); if (!TryStringToNumber(value, styles, ref number, info)) { @@ -1009,15 +1024,15 @@ internal static unsafe bool TryParseDouble(ReadOnlySpan value, NumberStyle if (valueTrim.EqualsOrdinalIgnoreCase(info.PositiveInfinitySymbol)) { - result = double.PositiveInfinity; + result = TFloat.PositiveInfinity; } else if (valueTrim.EqualsOrdinalIgnoreCase(info.NegativeInfinitySymbol)) { - result = double.NegativeInfinity; + result = TFloat.NegativeInfinity; } else if (valueTrim.EqualsOrdinalIgnoreCase(info.NaNSymbol)) { - result = double.NaN; + result = TFloat.NaN; } else if (valueTrim.StartsWith(info.PositiveSign, StringComparison.OrdinalIgnoreCase)) { @@ -1025,174 +1040,32 @@ internal static unsafe bool TryParseDouble(ReadOnlySpan value, NumberStyle if (valueTrim.EqualsOrdinalIgnoreCase(info.PositiveInfinitySymbol)) { - result = double.PositiveInfinity; + result = TFloat.PositiveInfinity; } else if (valueTrim.EqualsOrdinalIgnoreCase(info.NaNSymbol)) { - result = double.NaN; + result = TFloat.NaN; } else { - result = 0; + result = TFloat.Zero; return false; } } else if ((valueTrim.StartsWith(info.NegativeSign, StringComparison.OrdinalIgnoreCase) && valueTrim.Slice(info.NegativeSign.Length).EqualsOrdinalIgnoreCase(info.NaNSymbol)) || (info.AllowHyphenDuringParsing && SpanStartsWith(valueTrim, '-') && valueTrim.Slice(1).EqualsOrdinalIgnoreCase(info.NaNSymbol))) { - result = double.NaN; - } - else - { - result = 0; - return false; // We really failed - } - } - else - { - result = NumberToDouble(ref number); - } - - return true; - } - - internal static unsafe bool TryParseHalf(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out Half result) - { - NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, stackalloc byte[HalfNumberBufferLength]); - - if (!TryStringToNumber(value, styles, ref number, info)) - { - ReadOnlySpan valueTrim = value.Trim(); - - // 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. - // - // Additionally, since some cultures ("wo") actually define `PositiveInfinitySymbol` - // to include `PositiveSign`, we need to check whether `PositiveInfinitySymbol` fits - // that case so that we don't start parsing things like `++infini`. - - if (valueTrim.EqualsOrdinalIgnoreCase(info.PositiveInfinitySymbol)) - { - result = Half.PositiveInfinity; - } - else if (valueTrim.EqualsOrdinalIgnoreCase(info.NegativeInfinitySymbol)) - { - result = Half.NegativeInfinity; - } - else if (valueTrim.EqualsOrdinalIgnoreCase(info.NaNSymbol)) - { - result = Half.NaN; - } - else if (valueTrim.StartsWith(info.PositiveSign, StringComparison.OrdinalIgnoreCase)) - { - valueTrim = valueTrim.Slice(info.PositiveSign.Length); - - if (!info.PositiveInfinitySymbol.StartsWith(info.PositiveSign, StringComparison.OrdinalIgnoreCase) && valueTrim.EqualsOrdinalIgnoreCase(info.PositiveInfinitySymbol)) - { - result = Half.PositiveInfinity; - } - else if (!info.NaNSymbol.StartsWith(info.PositiveSign, StringComparison.OrdinalIgnoreCase) && valueTrim.EqualsOrdinalIgnoreCase(info.NaNSymbol)) - { - result = Half.NaN; - } - else - { - result = Half.Zero; - return false; - } - } - else if (valueTrim.StartsWith(info.NegativeSign, StringComparison.OrdinalIgnoreCase) && - !info.NaNSymbol.StartsWith(info.NegativeSign, StringComparison.OrdinalIgnoreCase) && - valueTrim.Slice(info.NegativeSign.Length).EqualsOrdinalIgnoreCase(info.NaNSymbol)) - { - result = Half.NaN; - } - else if (info.AllowHyphenDuringParsing && SpanStartsWith(valueTrim, '-') && !info.NaNSymbol.StartsWith(info.NegativeSign, StringComparison.OrdinalIgnoreCase) && - !info.NaNSymbol.StartsWith('-') && valueTrim.Slice(1).EqualsOrdinalIgnoreCase(info.NaNSymbol)) - { - result = Half.NaN; - } - else - { - result = Half.Zero; - return false; // We really failed - } - } - else - { - result = NumberToHalf(ref number); - } - - return true; - } - - internal static unsafe bool TryParseSingle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out float result) - { - NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, stackalloc byte[SingleNumberBufferLength]); - - if (!TryStringToNumber(value, styles, ref number, info)) - { - ReadOnlySpan valueTrim = value.Trim(); - - // 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. - // - // Additionally, since some cultures ("wo") actually define `PositiveInfinitySymbol` - // to include `PositiveSign`, we need to check whether `PositiveInfinitySymbol` fits - // that case so that we don't start parsing things like `++infini`. - - if (valueTrim.EqualsOrdinalIgnoreCase(info.PositiveInfinitySymbol)) - { - result = float.PositiveInfinity; - } - else if (valueTrim.EqualsOrdinalIgnoreCase(info.NegativeInfinitySymbol)) - { - result = float.NegativeInfinity; - } - else if (valueTrim.EqualsOrdinalIgnoreCase(info.NaNSymbol)) - { - result = float.NaN; - } - else if (valueTrim.StartsWith(info.PositiveSign, StringComparison.OrdinalIgnoreCase)) - { - valueTrim = valueTrim.Slice(info.PositiveSign.Length); - - if (!info.PositiveInfinitySymbol.StartsWith(info.PositiveSign, StringComparison.OrdinalIgnoreCase) && valueTrim.EqualsOrdinalIgnoreCase(info.PositiveInfinitySymbol)) - { - result = float.PositiveInfinity; - } - else if (!info.NaNSymbol.StartsWith(info.PositiveSign, StringComparison.OrdinalIgnoreCase) && valueTrim.EqualsOrdinalIgnoreCase(info.NaNSymbol)) - { - result = float.NaN; - } - else - { - result = 0; - return false; - } - } - else if (valueTrim.StartsWith(info.NegativeSign, StringComparison.OrdinalIgnoreCase) && - !info.NaNSymbol.StartsWith(info.NegativeSign, StringComparison.OrdinalIgnoreCase) && - valueTrim.Slice(info.NegativeSign.Length).EqualsOrdinalIgnoreCase(info.NaNSymbol)) - { - result = float.NaN; - } - else if (info.AllowHyphenDuringParsing && SpanStartsWith(valueTrim, '-') && !info.NaNSymbol.StartsWith(info.NegativeSign, StringComparison.OrdinalIgnoreCase) && - !info.NaNSymbol.StartsWith('-') && valueTrim.Slice(1).EqualsOrdinalIgnoreCase(info.NaNSymbol)) - { - result = float.NaN; + result = TFloat.NaN; } else { - result = 0; + result = TFloat.Zero; return false; // We really failed } } else { - result = NumberToSingle(ref number); + result = NumberToFloat(ref number); } return true; @@ -1358,67 +1231,24 @@ private static Exception GetExceptionUInt128(ParsingStatus status) => new FormatException(SR.Format_InvalidString) : new OverflowException(SR.Overflow_UInt128); - internal static double NumberToDouble(ref NumberBuffer number) - { - number.CheckConsistency(); - double result; - - if ((number.DigitsCount == 0) || (number.Scale < DoubleMinExponent)) - { - result = 0; - } - else if (number.Scale > DoubleMaxExponent) - { - result = double.PositiveInfinity; - } - else - { - ulong bits = NumberToDoubleFloatingPointBits(ref number, in FloatingPointInfo.Double); - result = BitConverter.UInt64BitsToDouble(bits); - } - - return number.IsNegative ? -result : result; - } - - internal static Half NumberToHalf(ref NumberBuffer number) - { - number.CheckConsistency(); - Half result; - - if ((number.DigitsCount == 0) || (number.Scale < HalfMinExponent)) - { - result = default; - } - else if (number.Scale > HalfMaxExponent) - { - result = Half.PositiveInfinity; - } - else - { - ushort bits = NumberToHalfFloatingPointBits(ref number, in FloatingPointInfo.Half); - result = new Half(bits); - } - - return number.IsNegative ? Half.Negate(result) : result; - } - - internal static float NumberToSingle(ref NumberBuffer number) + internal static TFloat NumberToFloat(ref NumberBuffer number) + where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo { number.CheckConsistency(); - float result; + TFloat result; - if ((number.DigitsCount == 0) || (number.Scale < SingleMinExponent)) + if ((number.DigitsCount == 0) || (number.Scale < TFloat.MinDecimalExponent)) { - result = 0; + result = TFloat.Zero; } - else if (number.Scale > SingleMaxExponent) + else if (number.Scale > TFloat.MaxDecimalExponent) { - result = float.PositiveInfinity; + result = TFloat.PositiveInfinity; } else { - uint bits = NumberToSingleFloatingPointBits(ref number, in FloatingPointInfo.Single); - result = BitConverter.UInt32BitsToSingle(bits); + ulong bits = NumberToFloatingPointBits(ref number); + result = TFloat.BitsToFloat(bits); } return number.IsNegative ? -result : result; diff --git a/src/libraries/System.Private.CoreLib/src/System/Single.cs b/src/libraries/System.Private.CoreLib/src/System/Single.cs index 188534808e3a7..f6c5b2e6fe057 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Single.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Single.cs @@ -32,7 +32,8 @@ public readonly struct Single IEquatable, IBinaryFloatingPointIeee754, IMinMaxValue, - IUtf8SpanFormattable + IUtf8SpanFormattable, + IBinaryFloatParseAndFormatInfo { private readonly float m_value; // Do not rename (binary serialization) @@ -107,6 +108,9 @@ public readonly struct Single internal const uint MinTrailingSignificand = 0x0000_0000; internal const uint MaxTrailingSignificand = 0x007F_FFFF; + internal const int TrailingSignificandLength = 23; + internal const int SignificandLength = TrailingSignificandLength + 1; + internal byte BiasedExponent { get @@ -375,33 +379,33 @@ public bool TryFormat(Span utf8Destination, out int bytesWritten, [StringS public static float Parse(string s) { if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseSingle(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo); + return Number.ParseFloat(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo); } public static float Parse(string s, NumberStyles style) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseSingle(s, style, NumberFormatInfo.CurrentInfo); + return Number.ParseFloat(s, style, NumberFormatInfo.CurrentInfo); } public static float Parse(string s, IFormatProvider? provider) { if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseSingle(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.GetInstance(provider)); + return Number.ParseFloat(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.GetInstance(provider)); } public static float Parse(string s, NumberStyles style, IFormatProvider? provider) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseSingle(s, style, NumberFormatInfo.GetInstance(provider)); + return Number.ParseFloat(s, style, NumberFormatInfo.GetInstance(provider)); } public static float Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Float | NumberStyles.AllowThousands, IFormatProvider? provider = null) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - return Number.ParseSingle(s, style, NumberFormatInfo.GetInstance(provider)); + return Number.ParseFloat(s, style, NumberFormatInfo.GetInstance(provider)); } public static bool TryParse([NotNullWhen(true)] string? s, out float result) @@ -441,7 +445,7 @@ public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatPro private static bool TryParse(ReadOnlySpan s, NumberStyles style, NumberFormatInfo info, out float result) { - return Number.TryParseSingle(s, style, info, out result); + return Number.TryParseFloat(s, style, info, out result); } // @@ -2057,6 +2061,46 @@ public static float TanPi(float x) /// static float IUnaryPlusOperators.operator +(float value) => (float)(+value); + // + // IBinaryFloatParseAndFormatInfo + // + + static int IBinaryFloatParseAndFormatInfo.NumberBufferLength => Number.SingleNumberBufferLength; + + static ulong IBinaryFloatParseAndFormatInfo.ZeroBits => 0; + static ulong IBinaryFloatParseAndFormatInfo.InfinityBits => 0x7F800000; + + static ulong IBinaryFloatParseAndFormatInfo.NormalMantissaMask => (1UL << SignificandLength) - 1; + static ulong IBinaryFloatParseAndFormatInfo.DenormalMantissaMask => TrailingSignificandMask; + + static int IBinaryFloatParseAndFormatInfo.MinBinaryExponent => 1 - MaxExponent; + static int IBinaryFloatParseAndFormatInfo.MaxBinaryExponent => MaxExponent; + + static int IBinaryFloatParseAndFormatInfo.MinDecimalExponent => -45; + static int IBinaryFloatParseAndFormatInfo.MaxDecimalExponent => 39; + + static int IBinaryFloatParseAndFormatInfo.ExponentBias => ExponentBias; + static ushort IBinaryFloatParseAndFormatInfo.ExponentBits => 8; + + static int IBinaryFloatParseAndFormatInfo.OverflowDecimalExponent => (MaxExponent + (2 * SignificandLength)) / 3; + static int IBinaryFloatParseAndFormatInfo.InfinityExponent => 0xFF; + + static ushort IBinaryFloatParseAndFormatInfo.NormalMantissaBits => SignificandLength; + static ushort IBinaryFloatParseAndFormatInfo.DenormalMantissaBits => TrailingSignificandLength; + + static int IBinaryFloatParseAndFormatInfo.MinFastFloatDecimalExponent => -65; + static int IBinaryFloatParseAndFormatInfo.MaxFastFloatDecimalExponent => 38; + + static int IBinaryFloatParseAndFormatInfo.MinExponentRoundToEven => -17; + static int IBinaryFloatParseAndFormatInfo.MaxExponentRoundToEven => 10; + + static int IBinaryFloatParseAndFormatInfo.MaxExponentFastPath => 10; + static ulong IBinaryFloatParseAndFormatInfo.MaxMantissaFastPath => 2UL << TrailingSignificandLength; + + static float IBinaryFloatParseAndFormatInfo.BitsToFloat(ulong bits) => BitConverter.UInt32BitsToSingle((uint)(bits)); + + static ulong IBinaryFloatParseAndFormatInfo.FloatToBits(float value) => BitConverter.SingleToUInt32Bits(value); + // // Helpers // From c1f35f37b96b76619a4c3d17713628b3fac2dba0 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 29 May 2023 09:59:28 -0700 Subject: [PATCH 04/19] Refactoring the primitive parsing logic to support UTF-8 --- .../System.Private.CoreLib.Shared.projitems | 10 +- .../System.Private.CoreLib/src/System/Byte.cs | 6 +- .../System.Private.CoreLib/src/System/Char.cs | 2 + .../src/System/Decimal.DecCalc.cs | 16 +- .../src/System/Decimal.cs | 16 +- .../src/System/Double.cs | 55 +- .../System.Private.CoreLib/src/System/Enum.cs | 4 +- .../System/Globalization/CompareInfo.Utf8.cs | 83 +++ .../src/System/Globalization/Ordinal.Utf8.cs | 254 ++++++++ .../src/System/Globalization/Ordinal.cs | 2 +- .../System.Private.CoreLib/src/System/Half.cs | 49 +- .../src/System/IUtfChar.cs | 3 + .../src/System/Int128.cs | 4 +- .../src/System/Int16.cs | 4 +- .../src/System/Int32.cs | 4 +- .../src/System/Int64.cs | 4 +- .../MemoryExtensions.Globalization.Utf8.cs | 74 +++ .../src/System/MemoryExtensions.Trim.Utf8.cs | 77 +++ .../src/System/Number.Parsing.cs | 565 ++++++++++++------ .../src/System/ParseNumbers.cs | 24 +- .../src/System/SByte.cs | 4 +- .../src/System/Single.cs | 55 +- .../src/System/Text/Unicode/Utf8Utility.cs | 126 ++++ .../src/System/UInt128.cs | 6 +- .../src/System/UInt16.cs | 4 +- .../src/System/UInt32.cs | 4 +- .../src/System/UInt64.cs | 4 +- 27 files changed, 1087 insertions(+), 372 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Utf8.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.Utf8.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Trim.Utf8.cs diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 66bd8772d1ce2..4319b0bfc3aa2 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -329,6 +329,7 @@ + @@ -375,6 +376,7 @@ + @@ -509,7 +511,7 @@ - + @@ -526,7 +528,9 @@ + + @@ -1275,7 +1279,7 @@ Common\Interop\Interop.Collation.cs - + Common\Interop\Interop.Collation.OSX.cs @@ -2618,4 +2622,4 @@ - \ 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 6716614028f66..0c02ae4171286 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Byte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Byte.cs @@ -108,7 +108,7 @@ public static byte Parse(string s, NumberStyles style, IFormatProvider? provider public static byte Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.ParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider)); + return Number.ParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider)); } public static bool TryParse([NotNullWhen(true)] string? s, out byte result) => TryParse(s, NumberStyles.Integer, provider: null, out result); @@ -124,7 +124,7 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I result = 0; return false; } - return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out byte result) @@ -1161,6 +1161,8 @@ static bool INumberBase.TryConvertToTruncating(byte value, [MaybeN static byte IUtfChar.CastFrom(uint value) => (byte)value; static byte IUtfChar.CastFrom(ulong value) => (byte)value; + static uint IUtfChar.CastToUInt32(byte value) => value; + // // IBinaryIntegerParseAndFormatInfo // diff --git a/src/libraries/System.Private.CoreLib/src/System/Char.cs b/src/libraries/System.Private.CoreLib/src/System/Char.cs index bac465382d004..60117c0651af5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Char.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Char.cs @@ -2000,6 +2000,8 @@ static bool INumberBase.TryConvertToTruncating(char value, [MaybeN static char IUtfChar.CastFrom(uint value) => (char)value; static char IUtfChar.CastFrom(ulong value) => (char)value; + static uint IUtfChar.CastToUInt32(char value) => value; + // // IBinaryIntegerParseAndFormatInfo // diff --git a/src/libraries/System.Private.CoreLib/src/System/Decimal.DecCalc.cs b/src/libraries/System.Private.CoreLib/src/System/Decimal.DecCalc.cs index 74e2a0d246b3e..179e864e95294 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Decimal.DecCalc.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Decimal.DecCalc.cs @@ -196,7 +196,7 @@ private static void UInt64x64To128(ulong a, ulong b, ref DecCalc result) high++; if (high > uint.MaxValue) - Number.ThrowOverflowException(TypeCode.Decimal); + Number.ThrowOverflowException(SR.Overflow_Decimal); result.Low64 = low; result.High = (uint)high; } @@ -681,7 +681,7 @@ private static unsafe int ScaleResult(Buf24* bufRes, uint hiRes, int scale) return scale; ThrowOverflow: - Number.ThrowOverflowException(TypeCode.Decimal); + Number.ThrowOverflowException(SR.Overflow_Decimal); return 0; } @@ -725,7 +725,7 @@ private static unsafe uint DivByConst(uint* result, uint hiRes, out uint quotien private static int OverflowUnscale(ref Buf12 bufQuo, int scale, bool sticky) { if (--scale < 0) - Number.ThrowOverflowException(TypeCode.Decimal); + Number.ThrowOverflowException(SR.Overflow_Decimal); Debug.Assert(bufQuo.U2 == 0); @@ -837,7 +837,7 @@ private static int SearchScale(ref Buf12 bufQuo, int scale) // positive if it isn't already. // if (curScale + scale < 0) - Number.ThrowOverflowException(TypeCode.Decimal); + Number.ThrowOverflowException(SR.Overflow_Decimal); return curScale; } @@ -1107,7 +1107,7 @@ internal static unsafe void DecAddSub(ref DecCalc d1, ref DecCalc d2, bool sign) // Divide the value by 10, dropping the scale factor. // if ((flags & ScaleMask) == 0) - Number.ThrowOverflowException(TypeCode.Decimal); + Number.ThrowOverflowException(SR.Overflow_Decimal); flags -= 1 << ScaleShift; const uint den = 10; @@ -1534,7 +1534,7 @@ internal static void VarDecFromR4(float input, out DecCalc result) return; // result should be zeroed out if (exp > 96) - Number.ThrowOverflowException(TypeCode.Decimal); + Number.ThrowOverflowException(SR.Overflow_Decimal); uint flags = 0; if (input < 0) @@ -1701,7 +1701,7 @@ internal static void VarDecFromR8(double input, out DecCalc result) return; // result should be zeroed out if (exp > 96) - Number.ThrowOverflowException(TypeCode.Decimal); + Number.ThrowOverflowException(SR.Overflow_Decimal); uint flags = 0; if (input < 0) @@ -2170,7 +2170,7 @@ internal static unsafe void VarDecDiv(ref DecCalc d1, ref DecCalc d2) } ThrowOverflow: - Number.ThrowOverflowException(TypeCode.Decimal); + Number.ThrowOverflowException(SR.Overflow_Decimal); } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs index 33b7acca438a2..1d487431ba226 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs @@ -732,10 +732,10 @@ public static byte ToByte(decimal value) } catch (OverflowException) { - Number.ThrowOverflowException(TypeCode.Byte); + Number.ThrowOverflowException(); throw; } - if (temp != (byte)temp) Number.ThrowOverflowException(TypeCode.Byte); + if (temp != (byte)temp) Number.ThrowOverflowException(); return (byte)temp; } @@ -753,10 +753,10 @@ public static sbyte ToSByte(decimal value) } catch (OverflowException) { - Number.ThrowOverflowException(TypeCode.SByte); + Number.ThrowOverflowException(); throw; } - if (temp != (sbyte)temp) Number.ThrowOverflowException(TypeCode.SByte); + if (temp != (sbyte)temp) Number.ThrowOverflowException(); return (sbyte)temp; } @@ -773,10 +773,10 @@ public static short ToInt16(decimal value) } catch (OverflowException) { - Number.ThrowOverflowException(TypeCode.Int16); + Number.ThrowOverflowException(); throw; } - if (temp != (short)temp) Number.ThrowOverflowException(TypeCode.Int16); + if (temp != (short)temp) Number.ThrowOverflowException(); return (short)temp; } @@ -848,10 +848,10 @@ public static ushort ToUInt16(decimal value) } catch (OverflowException) { - Number.ThrowOverflowException(TypeCode.UInt16); + Number.ThrowOverflowException(); throw; } - if (temp != (ushort)temp) Number.ThrowOverflowException(TypeCode.UInt16); + if (temp != (ushort)temp) Number.ThrowOverflowException(); return (ushort)temp; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Double.cs b/src/libraries/System.Private.CoreLib/src/System/Double.cs index 2c2ae1dbf6a11..ef0d4dbc75d05 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Double.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Double.cs @@ -373,30 +373,19 @@ public bool TryFormat(Span utf8Destination, out int bytesWritten, [StringS return Number.TryFormatDouble(m_value, format, NumberFormatInfo.GetInstance(provider), utf8Destination, out bytesWritten); } - public static double Parse(string s) - { - if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseFloat(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo); - } + public static double Parse(string s) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null); - public static double Parse(string s, NumberStyles style) - { - NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseFloat(s, style, NumberFormatInfo.CurrentInfo); - } + public static double Parse(string s, NumberStyles style) => Parse(s, style, provider: null); - public static double Parse(string s, IFormatProvider? provider) - { - if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseFloat(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.GetInstance(provider)); - } + public static double Parse(string s, IFormatProvider? provider) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider); public static double Parse(string s, NumberStyles style, IFormatProvider? provider) { - NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseFloat(s, style, NumberFormatInfo.GetInstance(provider)); + if (s is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); + } + return Parse(s.AsSpan(), style, provider); } // Parses a double from a String in the given style. If @@ -410,24 +399,12 @@ public static double Parse(string s, NumberStyles style, IFormatProvider? provid public static double Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Float | NumberStyles.AllowThousands, IFormatProvider? provider = null) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - return Number.ParseFloat(s, style, NumberFormatInfo.GetInstance(provider)); + return Number.ParseFloat(s, style, NumberFormatInfo.GetInstance(provider)); } - public static bool TryParse([NotNullWhen(true)] string? s, out double result) - { - if (s == null) - { - result = 0; - return false; - } - - return TryParse((ReadOnlySpan)s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo, out result); - } + public static bool TryParse([NotNullWhen(true)] string? s, out double result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null, out result); - public static bool TryParse(ReadOnlySpan s, out double result) - { - return TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo, out result); - } + public static bool TryParse(ReadOnlySpan s, out double result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null, out result); public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out double result) { @@ -438,19 +415,13 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I result = 0; return false; } - - return TryParse((ReadOnlySpan)s, style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result); } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out double result) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - return TryParse(s, style, NumberFormatInfo.GetInstance(provider), out result); - } - - private static bool TryParse(ReadOnlySpan s, NumberStyles style, NumberFormatInfo info, out double result) - { - return Number.TryParseFloat(s, style, info, out result); + return Number.TryParseFloat(s, style, NumberFormatInfo.GetInstance(provider), out result); } // diff --git a/src/libraries/System.Private.CoreLib/src/System/Enum.cs b/src/libraries/System.Private.CoreLib/src/System/Enum.cs index bb7fbaa73fffa..99d7cc3dd0e45 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Enum.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Enum.cs @@ -956,7 +956,7 @@ private static unsafe bool TryParseByValueOrName( if (throwOnFailure) { - Number.ThrowOverflowException(Type.GetTypeCode(typeof(TUnderlying))); + Number.ThrowOverflowException(); } } @@ -1023,7 +1023,7 @@ private static unsafe bool TryParseRareTypeByValueOrName( if (throwOnFailure) { - Number.ThrowOverflowException(Type.GetTypeCode(typeof(TUnderlying))); + ThrowHelper.ThrowOverflowException(); } #else throw CreateUnknownEnumTypeException(); diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Utf8.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Utf8.cs new file mode 100644 index 0000000000000..789d764564646 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Utf8.cs @@ -0,0 +1,83 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text; + +namespace System.Globalization +{ + public partial class CompareInfo + { + /// + /// Determines whether a UTF-8 string starts with a specific prefix. + /// + /// The UTF-8 string to search within. + /// The prefix to attempt to match at the start of . + /// The to use during the match. + /// + /// if occurs at the start of ; + /// otherwise, . + /// + /// + /// contains an unsupported combination of flags. + /// + internal bool IsPrefixUtf8(ReadOnlySpan source, ReadOnlySpan prefix, CompareOptions options = CompareOptions.None) + { + // The empty UTF-8 string is trivially a prefix of every other string. For compat with + // earlier versions of the Framework we'll early-exit here before validating the + // 'options' argument. + + if (prefix.IsEmpty) + { + return true; + } + + if ((options & ValidIndexMaskOffFlags) == 0) + { + // Common case: caller is attempting to perform a linguistic search. + // Pass the flags down to NLS or ICU unless we're running in invariant + // mode, at which point we normalize the flags to Ordinal[IgnoreCase]. + + if (!GlobalizationMode.Invariant) + { + return StartsWithCoreUtf8(source, prefix, options); + } + + if ((options & CompareOptions.IgnoreCase) == 0) + { + return source.StartsWith(prefix); + } + + return source.StartsWithOrdinalIgnoreCaseUtf8(prefix); + } + else + { + // Less common case: caller is attempting to perform non-linguistic comparison, + // or an invalid combination of flags was supplied. + + if (options == CompareOptions.Ordinal) + { + return source.StartsWith(prefix); + } + + if (options == CompareOptions.OrdinalIgnoreCase) + { + return source.StartsWithOrdinalIgnoreCaseUtf8(prefix); + } + + ThrowCompareOptionsCheckFailed(options); + + return false; // make the compiler happy; + } + } + + private unsafe bool StartsWithCoreUtf8(ReadOnlySpan source, ReadOnlySpan prefix, CompareOptions options) + { + // NLS/ICU doesn't provide native UTF-8 support so we need to convert to UTF-16 and compare that way + + ReadOnlySpan sourceUtf16 = Encoding.Unicode.GetString(source); + ReadOnlySpan prefixUtf16 = Encoding.Unicode.GetString(prefix); + + return StartsWithCore(sourceUtf16, prefixUtf16, options, matchLengthPtr: null); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs new file mode 100644 index 0000000000000..4c5d10bc8f3c1 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs @@ -0,0 +1,254 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Text; +using System.Text.Unicode; + +namespace System.Globalization +{ + internal static partial class Ordinal + { + internal static int CompareStringIgnoreCaseUtf8(ref byte strA, int lengthA, ref byte strB, int lengthB) + { + int length = Math.Min(lengthA, lengthB); + int range = length; + + ref byte charA = ref strA; + ref byte charB = ref strB; + + const byte maxChar = 0x7F; + + while ((length != 0) && (charA <= maxChar) && (charB <= maxChar)) + { + // Ordinal equals or lowercase equals if the result ends up in the a-z range + if (charA == charB || + ((charA | 0x20) == (charB | 0x20) && char.IsAsciiLetter((char)charA))) + { + length--; + charA = ref Unsafe.Add(ref charA, 1); + charB = ref Unsafe.Add(ref charB, 1); + } + else + { + int currentA = charA; + int currentB = charB; + + // Uppercase both chars if needed + if (char.IsAsciiLetterLower((char)charA)) + { + currentA -= 0x20; + } + if (char.IsAsciiLetterLower((char)charB)) + { + currentB -= 0x20; + } + + // Return the (case-insensitive) difference between them. + return currentA - currentB; + } + } + + if (length == 0) + { + return lengthA - lengthB; + } + + range -= length; + + return CompareStringIgnoreCaseNonAsciiUtf8(ref charA, lengthA - range, ref charB, lengthB - range); + } + + internal static int CompareStringIgnoreCaseNonAsciiUtf8(ref byte strA, int lengthA, ref byte strB, int lengthB) + { + // NLS/ICU doesn't provide native UTF-8 support so we need to convert to UTF-16 and compare that way + + ReadOnlySpan stringAUtf16 = Encoding.Unicode.GetString(MemoryMarshal.CreateReadOnlySpan(ref strA, lengthA)); + ReadOnlySpan stringBUtf16 = Encoding.Unicode.GetString(MemoryMarshal.CreateReadOnlySpan(ref strB, lengthB)); + + return CompareStringIgnoreCaseNonAscii( + ref MemoryMarshal.GetReference(stringAUtf16), stringAUtf16.Length, + ref MemoryMarshal.GetReference(stringBUtf16), stringBUtf16.Length + ); + } + + private static bool EqualsIgnoreCaseUtf8_Vector128(ref byte charA, ref byte charB, int length) + { + Debug.Assert(length >= Vector128.Count); + Debug.Assert(Vector128.IsHardwareAccelerated); + + nuint lengthU = (nuint)length; + nuint lengthToExamine = lengthU - (nuint)Vector128.Count; + nuint i = 0; + Vector128 vec1; + Vector128 vec2; + do + { + vec1 = Vector128.LoadUnsafe(ref charA, i); + vec2 = Vector128.LoadUnsafe(ref charB, i); + + if (!Utf8Utility.AllBytesInVector128AreAscii(vec1 | vec2)) + { + goto NON_ASCII; + } + + if (!Utf8Utility.Vector128OrdinalIgnoreCaseAscii(vec1, vec2)) + { + return false; + } + + i += (nuint)Vector128.Count; + } while (i <= lengthToExamine); + + // Use scalar path for trailing elements + return i == lengthU || EqualsIgnoreCaseUtf8(ref Unsafe.Add(ref charA, i), ref Unsafe.Add(ref charB, i), (int)(lengthU - i)); + + NON_ASCII: + if (Utf8Utility.AllBytesInVector128AreAscii(vec1) || Utf8Utility.AllBytesInVector128AreAscii(vec2)) + { + // No need to use the fallback if one of the inputs is full-ASCII + return false; + } + + // Fallback for Non-ASCII inputs + return CompareStringIgnoreCaseUtf8( + ref Unsafe.Add(ref charA, i), (int)(lengthU - i), + ref Unsafe.Add(ref charB, i), (int)(lengthU - i) + ) == 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool EqualsIgnoreCaseUtf8(ref byte charA, ref byte charB, int length) + { + if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + { + return EqualsIgnoreCaseUtf8_Scalar(ref charA, ref charB, length); + } + + return EqualsIgnoreCaseUtf8_Vector128(ref charA, ref charB, length); + } + + internal static bool EqualsIgnoreCaseUtf8_Scalar(ref byte charA, ref byte charB, int length) + { + IntPtr byteOffset = IntPtr.Zero; + +#if TARGET_64BIT + ulong valueAu64 = 0; + ulong valueBu64 = 0; + // Read 8 chars (64 bits) at a time from each string + while ((uint)length >= 8) + { + valueAu64 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charA, byteOffset)); + valueBu64 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charB, byteOffset)); + + // A 32-bit test - even with the bit-twiddling here - is more efficient than a 64-bit test. + ulong temp = valueAu64 | valueBu64; + if (!Utf8Utility.AllBytesInUInt32AreAscii((uint)temp | (uint)(temp >> 32))) + { + goto NonAscii64; // one of the inputs contains non-ASCII data + } + + // Generally, the caller has likely performed a first-pass check that the input strings + // are likely equal. Consider a dictionary which computes the hash code of its key before + // performing a proper deep equality check of the string contents. We want to optimize for + // the case where the equality check is likely to succeed, which means that we want to avoid + // branching within this loop unless we're about to exit the loop, either due to failure or + // due to us running out of input data. + + if (!Utf8Utility.UInt64OrdinalIgnoreCaseAscii(valueAu64, valueBu64)) + { + return false; + } + + byteOffset += 8; + length -= 4; + } +#endif + uint valueAu32 = 0; + uint valueBu32 = 0; + // Read 2 chars (32 bits) at a time from each string +#if TARGET_64BIT + if ((uint)length >= 2) +#else + while ((uint)length >= 2) +#endif + { + valueAu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charA, byteOffset)); + valueBu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charB, byteOffset)); + + if (!Utf8Utility.AllBytesInUInt32AreAscii(valueAu32 | valueBu32)) + { + goto NonAscii32; // one of the inputs contains non-ASCII data + } + + // Generally, the caller has likely performed a first-pass check that the input strings + // are likely equal. Consider a dictionary which computes the hash code of its key before + // performing a proper deep equality check of the string contents. We want to optimize for + // the case where the equality check is likely to succeed, which means that we want to avoid + // branching within this loop unless we're about to exit the loop, either due to failure or + // due to us running out of input data. + + if (!Utf8Utility.UInt32OrdinalIgnoreCaseAscii(valueAu32, valueBu32)) + { + return false; + } + + byteOffset += 4; + length -= 2; + } + + if (length != 0) + { + Debug.Assert(length == 1); + + valueAu32 = Unsafe.AddByteOffset(ref charA, byteOffset); + valueBu32 = Unsafe.AddByteOffset(ref charB, byteOffset); + + if ((valueAu32 | valueBu32) > 0x7Fu) + { + goto NonAscii32; // one of the inputs contains non-ASCII data + } + + if (valueAu32 == valueBu32) + { + return true; // exact match + } + + valueAu32 |= 0x20u; + if ((uint)(valueAu32 - 'a') > (uint)('z' - 'a')) + { + return false; // not exact match, and first input isn't in [A-Za-z] + } + + return valueAu32 == (valueBu32 | 0x20u); + } + + Debug.Assert(length == 0); + return true; + + NonAscii32: + // Both values have to be non-ASCII to use the slow fallback, in case if one of them is not we return false + if (Utf8Utility.AllBytesInUInt32AreAscii(valueAu32) || Utf8Utility.AllBytesInUInt32AreAscii(valueBu32)) + { + return false; + } + goto NonAscii; + +#if TARGET_64BIT + NonAscii64: + // Both values have to be non-ASCII to use the slow fallback, in case if one of them is not we return false + if (Utf8Utility.AllBytesInUInt64AreAscii(valueAu64) || Utf8Utility.AllBytesInUInt64AreAscii(valueBu64)) + { + return false; + } +#endif + NonAscii: + // The non-ASCII case is factored out into its own helper method so that the JIT + // doesn't need to emit a complex prolog for its caller (this method). + return CompareStringIgnoreCaseUtf8(ref Unsafe.AddByteOffset(ref charA, byteOffset), length, ref Unsafe.AddByteOffset(ref charB, byteOffset), length) == 0; + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.cs index d4a5ae73adf0d..01b873b2253ae 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.cs @@ -21,7 +21,7 @@ internal static int CompareStringIgnoreCase(ref char strA, int lengthA, ref char ref char charA = ref strA; ref char charB = ref strB; - char maxChar = (char)0x7F; + const char maxChar = (char)0x7F; while (length != 0 && charA <= maxChar && charB <= maxChar) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs index 93d149cf11687..48ac9366c06fa 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -286,11 +286,7 @@ public static bool IsSubnormal(Half value) /// /// The input to be parsed. /// The equivalent value representing the input string. If the input exceeds Half's range, a or is returned. - public static Half Parse(string s) - { - if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseFloat(s, DefaultParseStyle, NumberFormatInfo.CurrentInfo); - } + public static Half Parse(string s) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null); /// /// Parses a from a in the given . @@ -298,12 +294,7 @@ public static Half Parse(string s) /// The input to be parsed. /// The used to parse the input. /// The equivalent value representing the input string. If the input exceeds Half's range, a or is returned. - public static Half Parse(string s, NumberStyles style) - { - NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseFloat(s, style, NumberFormatInfo.CurrentInfo); - } + public static Half Parse(string s, NumberStyles style) => Parse(s, style, provider: null); /// /// Parses a from a and . @@ -311,11 +302,7 @@ public static Half Parse(string s, NumberStyles style) /// The input to be parsed. /// A format provider. /// The equivalent value representing the input string. If the input exceeds Half's range, a or is returned. - public static Half Parse(string s, IFormatProvider? provider) - { - if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseFloat(s, DefaultParseStyle, NumberFormatInfo.GetInstance(provider)); - } + public static Half Parse(string s, IFormatProvider? provider) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider); /// /// Parses a from a with the given and . @@ -326,9 +313,11 @@ public static Half Parse(string s, IFormatProvider? provider) /// The equivalent value representing the input string. If the input exceeds Half's range, a or is returned. public static Half Parse(string s, NumberStyles style = DefaultParseStyle, IFormatProvider? provider = null) { - NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseFloat(s, style, NumberFormatInfo.GetInstance(provider)); + if (s is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); + } + return Parse(s.AsSpan(), style, provider); } /// @@ -341,7 +330,7 @@ public static Half Parse(string s, NumberStyles style = DefaultParseStyle, IForm public static Half Parse(ReadOnlySpan s, NumberStyles style = DefaultParseStyle, IFormatProvider? provider = null) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - return Number.ParseFloat(s, style, NumberFormatInfo.GetInstance(provider)); + return Number.ParseFloat(s, style, NumberFormatInfo.GetInstance(provider)); } /// @@ -350,15 +339,7 @@ public static Half Parse(ReadOnlySpan s, NumberStyles style = DefaultParse /// The input to be parsed. /// The equivalent value representing the input string if the parse was successful. If the input exceeds Half's range, a or is returned. If the parse was unsuccessful, a default value is returned. /// if the parse was successful, otherwise. - public static bool TryParse([NotNullWhen(true)] string? s, out Half result) - { - if (s == null) - { - result = default; - return false; - } - return TryParse(s, DefaultParseStyle, provider: null, out result); - } + public static bool TryParse([NotNullWhen(true)] string? s, out Half result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null, out result); /// /// Tries to parse a from a in the default parse style. @@ -366,10 +347,7 @@ public static bool TryParse([NotNullWhen(true)] string? s, out Half result) /// The input to be parsed. /// The equivalent value representing the input string if the parse was successful. If the input exceeds Half's range, a or is returned. If the parse was unsuccessful, a default value is returned. /// if the parse was successful, otherwise. - public static bool TryParse(ReadOnlySpan s, out Half result) - { - return TryParse(s, DefaultParseStyle, provider: null, out result); - } + public static bool TryParse(ReadOnlySpan s, out Half result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null, out result); /// /// Tries to parse a from a with the given and . @@ -385,11 +363,10 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I if (s == null) { - result = default; + result = Zero; return false; } - - return TryParse(s.AsSpan(), style, provider, out result); + return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result); } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/IUtfChar.cs b/src/libraries/System.Private.CoreLib/src/System/IUtfChar.cs index 09d8ba184436f..25f371ada6213 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IUtfChar.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IUtfChar.cs @@ -28,5 +28,8 @@ internal interface IUtfChar : /// Casts the specified value to this type. public static abstract TSelf CastFrom(ulong value); + + /// Casts a value of this type to an UInt32. + public static abstract uint CastToUInt32(TSelf value); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Int128.cs b/src/libraries/System.Private.CoreLib/src/System/Int128.cs index c6b98d28526c9..4539d045d00e9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int128.cs @@ -141,7 +141,7 @@ public static Int128 Parse(string s, NumberStyles style, IFormatProvider? provid public static Int128 Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.ParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider)); + return Number.ParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider)); } public static bool TryParse([NotNullWhen(true)] string? s, out Int128 result) => TryParse(s, NumberStyles.Integer, provider: null, out result); @@ -157,7 +157,7 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I result = 0; return false; } - return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out Int128 result) diff --git a/src/libraries/System.Private.CoreLib/src/System/Int16.cs b/src/libraries/System.Private.CoreLib/src/System/Int16.cs index 02d896f981fb3..286742abf8d30 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int16.cs @@ -141,7 +141,7 @@ public static short Parse(string s, NumberStyles style, IFormatProvider? provide public static short Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.ParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider)); + return Number.ParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider)); } public static bool TryParse([NotNullWhen(true)] string? s, out short result) => TryParse(s, NumberStyles.Integer, provider: null, out result); @@ -157,7 +157,7 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I result = 0; return false; } - return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out short result) diff --git a/src/libraries/System.Private.CoreLib/src/System/Int32.cs b/src/libraries/System.Private.CoreLib/src/System/Int32.cs index a0c055cd21a34..1097eb4b5dc73 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int32.cs @@ -151,7 +151,7 @@ public static int Parse(string s, NumberStyles style, IFormatProvider? provider) public static int Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.ParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider)); + return Number.ParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider)); } public static bool TryParse([NotNullWhen(true)] string? s, out int result) => TryParse(s, NumberStyles.Integer, provider: null, out result); @@ -167,7 +167,7 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I result = 0; return false; } - return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out int result) diff --git a/src/libraries/System.Private.CoreLib/src/System/Int64.cs b/src/libraries/System.Private.CoreLib/src/System/Int64.cs index 7f4ace2b1c725..4db12f8b80ec3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int64.cs @@ -148,7 +148,7 @@ public static long Parse(string s, NumberStyles style, IFormatProvider? provider public static long Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.ParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider)); + return Number.ParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider)); } public static bool TryParse([NotNullWhen(true)] string? s, out long result) => TryParse(s, NumberStyles.Integer, provider: null, out result); @@ -164,7 +164,7 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I result = 0; return false; } - return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out long result) diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.Utf8.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.Utf8.cs new file mode 100644 index 0000000000000..fdec0bab4f6e5 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.Utf8.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System +{ + public static partial class MemoryExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool EqualsOrdinalIgnoreCaseUtf8(this ReadOnlySpan span, ReadOnlySpan value) + { + if (span.Length != value.Length) + { + return false; + } + + if (value.Length == 0) // span.Length == value.Length == 0 + { + return true; + } + + return Ordinal.EqualsIgnoreCaseUtf8(ref MemoryMarshal.GetReference(span), ref MemoryMarshal.GetReference(value), span.Length); + } + + /// + /// Determines whether the beginning of the matches the specified when compared using the specified option. + /// + /// The source span. + /// The sequence to compare to the beginning of the source span. + /// One of the enumeration values that determines how the and are compared. + [Intrinsic] // Unrolled and vectorized for half-constant input (Ordinal) + internal static bool StartsWithUtf8(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) + { + string.CheckStringComparison(comparisonType); + + switch (comparisonType) + { + case StringComparison.CurrentCulture: + case StringComparison.CurrentCultureIgnoreCase: + { + return CultureInfo.CurrentCulture.CompareInfo.IsPrefixUtf8(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType)); + } + + case StringComparison.InvariantCulture: + case StringComparison.InvariantCultureIgnoreCase: + { + return CompareInfo.Invariant.IsPrefixUtf8(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType)); + } + + case StringComparison.Ordinal: + { + return span.StartsWith(value); + } + + default: + { + Debug.Assert(comparisonType == StringComparison.OrdinalIgnoreCase); + return span.StartsWithOrdinalIgnoreCaseUtf8(value); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool StartsWithOrdinalIgnoreCaseUtf8(this ReadOnlySpan span, ReadOnlySpan value) + { + return (value.Length <= span.Length) + && Ordinal.EqualsIgnoreCaseUtf8(ref MemoryMarshal.GetReference(span), ref MemoryMarshal.GetReference(value), value.Length); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Trim.Utf8.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Trim.Utf8.cs new file mode 100644 index 0000000000000..0f1c3b6da08d4 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Trim.Utf8.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Text; + +namespace System +{ + public static partial class MemoryExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ReadOnlySpan TrimUtf8(this ReadOnlySpan span) + { + // Assume that in most cases input doesn't need trimming + // + // Since `DecodeFromUtf8` and `DecodeLastFromUtf8` return `Rune.ReplacementChar` + // on failure and that is not whitespace, we can safely treat it as no trimming + // and leave failure handling up to the caller instead + + Debug.Assert(!Rune.IsWhiteSpace(Rune.ReplacementChar)); + + if (span.Length == 0) + { + return span; + } + + _ = Rune.DecodeFromUtf8(span, out Rune first, out int firstBytesConsumed); + + if (Rune.IsWhiteSpace(first)) + { + span = span[firstBytesConsumed..]; + return TrimFallback(span); + } + + _ = Rune.DecodeLastFromUtf8(span, out Rune last, out int lastBytesConsumed); + + if (Rune.IsWhiteSpace(last)) + { + span = span[..^lastBytesConsumed]; + return TrimFallback(span); + } + + return span; + + [MethodImpl(MethodImplOptions.NoInlining)] + static ReadOnlySpan TrimFallback(ReadOnlySpan span) + { + while (span.Length != 0) + { + _ = Rune.DecodeFromUtf8(span, out Rune current, out int bytesConsumed); + + if (!Rune.IsWhiteSpace(current)) + { + break; + } + + span = span[bytesConsumed..]; + } + + while (span.Length != 0) + { + _ = Rune.DecodeLastFromUtf8(span, out Rune current, out int bytesConsumed); + + if (!Rune.IsWhiteSpace(current)) + { + break; + } + + span = span[..^bytesConsumed]; + } + + return span; + } + } + } +} 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 c06f1fb4c6e6b..56da66e7066a4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -7,6 +7,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Text; namespace System { @@ -20,7 +21,7 @@ namespace System // // Numeric strings produced by the Format methods using the Currency, // Decimal, Engineering, Fixed point, General, or Number standard formats - // (the C, D, E, F, G, and N format specifiers) are guaranteed to be parseable + // (the C, D, E, F, G, and N format specifiers) are guaranteed to be parsable // by the Parse methods if the NumberStyles.Any style is // specified. Note, however, that the Parse methods do not accept // NaNs or Infinities. @@ -159,19 +160,21 @@ private static unsafe bool TryNumberBufferToBinaryInteger(ref NumberBu return true; } - internal static TInteger ParseBinaryInteger(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) + internal static TInteger ParseBinaryInteger(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) + where TChar : unmanaged, IUtfChar where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo { ParsingStatus status = TryParseBinaryInteger(value, styles, info, out TInteger result); if (status != ParsingStatus.OK) { - ThrowOverflowOrFormatException(status, value); + ThrowOverflowOrFormatException(status, value); } return result; } - private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info) + private static unsafe bool TryParseNumber(scoped ref TChar* str, TChar* strEnd, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info) + where TChar : unmanaged, IUtfChar { Debug.Assert(str != null); Debug.Assert(strEnd != null); @@ -192,39 +195,39 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu number.CheckConsistency(); - string decSep; // decimal separator from NumberFormatInfo. - string groupSep; // group separator from NumberFormatInfo. - string? currSymbol = null; // currency symbol from NumberFormatInfo. + ReadOnlySpan decSep; // decimal separator from NumberFormatInfo. + ReadOnlySpan groupSep; // group separator from NumberFormatInfo. + ReadOnlySpan currSymbol = ReadOnlySpan.Empty; // currency symbol from NumberFormatInfo. bool parsingCurrency = false; if ((styles & NumberStyles.AllowCurrencySymbol) != 0) { - currSymbol = info.CurrencySymbol; + currSymbol = info.CurrencySymbolTChar(); // The idea here is to match the currency separators and on failure match the number separators to keep the perf of VB's IsNumeric fast. // The values of decSep are setup to use the correct relevant separator (currency in the if part and decimal in the else part). - decSep = info.CurrencyDecimalSeparator; - groupSep = info.CurrencyGroupSeparator; + decSep = info.CurrencyDecimalSeparatorTChar(); + groupSep = info.CurrencyGroupSeparatorTChar(); parsingCurrency = true; } else { - decSep = info.NumberDecimalSeparator; - groupSep = info.NumberGroupSeparator; + decSep = info.NumberDecimalSeparatorTChar(); + groupSep = info.NumberGroupSeparatorTChar(); } int state = 0; - char* p = str; - char ch = p < strEnd ? *p : '\0'; - char* next; + TChar* p = str; + uint ch = (p < strEnd) ? TChar.CastToUInt32(*p) : '\0'; + TChar* next; while (true) { // Eat whitespace unless we've found a sign which isn't followed by a currency symbol. // "-Kr 1231.47" is legal but "- 1231.47" is not. - if (!IsWhite(ch) || (styles & NumberStyles.AllowLeadingWhite) == 0 || ((state & StateSign) != 0 && ((state & StateCurrency) == 0 && info.NumberNegativePattern != 2))) + if (!IsWhite(ch) || (styles & NumberStyles.AllowLeadingWhite) == 0 || ((state & StateSign) != 0 && (state & StateCurrency) == 0 && info.NumberNegativePattern != 2)) { - if ((((styles & NumberStyles.AllowLeadingSign) != 0) && (state & StateSign) == 0) && ((next = MatchChars(p, strEnd, info.PositiveSign)) != null || ((next = MatchNegativeSignChars(p, strEnd, info)) != null && (number.IsNegative = true)))) + if (((styles & NumberStyles.AllowLeadingSign) != 0) && (state & StateSign) == 0 && ((next = MatchChars(p, strEnd, info.PositiveSignTChar())) != null || ((next = MatchNegativeSignChars(p, strEnd, info)) != null && (number.IsNegative = true)))) { state |= StateSign; p = next - 1; @@ -247,7 +250,7 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu break; } } - ch = ++p < strEnd ? *p : '\0'; + ch = ++p < strEnd ? TChar.CastToUInt32(*p) : '\0'; } int digCount = 0; @@ -265,7 +268,7 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu { if (digCount < maxDigCount) { - number.Digits[digCount] = (byte)(ch); + number.Digits[digCount] = (byte)ch; if ((ch != '0') || (number.Kind != NumberBufferKind.Integer)) { digEnd = digCount + 1; @@ -308,12 +311,12 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu number.Scale--; } } - else if (((styles & NumberStyles.AllowDecimalPoint) != 0) && ((state & StateDecimal) == 0) && ((next = MatchChars(p, strEnd, decSep)) != null || (parsingCurrency && (state & StateCurrency) == 0) && (next = MatchChars(p, strEnd, info.NumberDecimalSeparator)) != null)) + else if (((styles & NumberStyles.AllowDecimalPoint) != 0) && ((state & StateDecimal) == 0) && ((next = MatchChars(p, strEnd, decSep)) != null || (parsingCurrency && (state & StateCurrency) == 0 && (next = MatchChars(p, strEnd, info.NumberDecimalSeparatorTChar())) != null))) { state |= StateDecimal; p = next - 1; } - else if (((styles & NumberStyles.AllowThousands) != 0) && ((state & StateDigits) != 0) && ((state & StateDecimal) == 0) && ((next = MatchChars(p, strEnd, groupSep)) != null || (parsingCurrency && (state & StateCurrency) == 0) && (next = MatchChars(p, strEnd, info.NumberGroupSeparator)) != null)) + else if (((styles & NumberStyles.AllowThousands) != 0) && ((state & StateDigits) != 0) && ((state & StateDecimal) == 0) && ((next = MatchChars(p, strEnd, groupSep)) != null || (parsingCurrency && (state & StateCurrency) == 0 && (next = MatchChars(p, strEnd, info.NumberGroupSeparatorTChar())) != null))) { p = next - 1; } @@ -321,25 +324,25 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu { break; } - ch = ++p < strEnd ? *p : '\0'; + ch = ++p < strEnd ? TChar.CastToUInt32(*p) : '\0'; } bool negExp = false; number.DigitsCount = digEnd; - number.Digits[digEnd] = (byte)('\0'); + number.Digits[digEnd] = (byte)'\0'; if ((state & StateDigits) != 0) { if ((ch == 'E' || ch == 'e') && ((styles & NumberStyles.AllowExponent) != 0)) { - char* temp = p; - ch = ++p < strEnd ? *p : '\0'; - if ((next = MatchChars(p, strEnd, info._positiveSign)) != null) + TChar* temp = p; + ch = ++p < strEnd ? TChar.CastToUInt32(*p) : '\0'; + if ((next = MatchChars(p, strEnd, info.PositiveSignTChar())) != null) { - ch = (p = next) < strEnd ? *p : '\0'; + ch = (p = next) < strEnd ? TChar.CastToUInt32(*p) : '\0'; } else if ((next = MatchNegativeSignChars(p, strEnd, info)) != null) { - ch = (p = next) < strEnd ? *p : '\0'; + ch = (p = next) < strEnd ? TChar.CastToUInt32(*p) : '\0'; negExp = true; } if (IsDigit(ch)) @@ -347,14 +350,14 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu int exp = 0; do { - exp = exp * 10 + (ch - '0'); - ch = ++p < strEnd ? *p : '\0'; + exp = (exp * 10) + (int)(ch - '0'); + ch = ++p < strEnd ? TChar.CastToUInt32(*p) : '\0'; if (exp > 1000) { exp = 9999; while (IsDigit(ch)) { - ch = ++p < strEnd ? *p : '\0'; + ch = ++p < strEnd ? TChar.CastToUInt32(*p) : '\0'; } } } while (IsDigit(ch)); @@ -367,7 +370,7 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu else { p = temp; - ch = p < strEnd ? *p : '\0'; + ch = p < strEnd ? TChar.CastToUInt32(*p) : '\0'; } } @@ -380,7 +383,7 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu numberOfTrailingZeros = Math.Min(numberOfTrailingZeros, numberOfFractionalDigits); Debug.Assert(numberOfTrailingZeros >= 0); number.DigitsCount = digEnd - numberOfTrailingZeros; - number.Digits[number.DigitsCount] = (byte)('\0'); + number.Digits[number.DigitsCount] = (byte)'\0'; } } @@ -388,7 +391,7 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu { if (!IsWhite(ch) || (styles & NumberStyles.AllowTrailingWhite) == 0) { - if ((styles & NumberStyles.AllowTrailingSign) != 0 && ((state & StateSign) == 0) && ((next = MatchChars(p, strEnd, info.PositiveSign)) != null || (((next = MatchNegativeSignChars(p, strEnd, info)) != null) && (number.IsNegative = true)))) + if ((styles & NumberStyles.AllowTrailingSign) != 0 && ((state & StateSign) == 0) && ((next = MatchChars(p, strEnd, info.PositiveSignTChar())) != null || (((next = MatchNegativeSignChars(p, strEnd, info)) != null) && (number.IsNegative = true)))) { state |= StateSign; p = next - 1; @@ -407,7 +410,7 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu break; } } - ch = ++p < strEnd ? *p : '\0'; + ch = ++p < strEnd ? TChar.CastToUInt32(*p) : '\0'; } if ((state & StateParens) == 0) { @@ -431,7 +434,8 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu } [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) + where TChar : unmanaged, IUtfChar where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo { if ((styles & ~NumberStyles.Integer) == 0) @@ -447,13 +451,14 @@ internal static ParsingStatus TryParseBinaryInteger(ReadOnlySpan if ((styles & NumberStyles.AllowBinarySpecifier) != 0) { - return TryParseBinaryIntegerHexOrBinaryNumberStyle>(value, styles, out result); + return TryParseBinaryIntegerHexOrBinaryNumberStyle>(value, styles, out result); } return TryParseBinaryIntegerNumber(value, styles, info, out result); } - 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) + where TChar : unmanaged, IUtfChar where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo { result = TInteger.Zero; @@ -473,16 +478,19 @@ private static unsafe ParsingStatus TryParseBinaryIntegerNumber(ReadOn } /// 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) + where TChar : unmanaged, IUtfChar where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo { Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format"); if (value.IsEmpty) + { goto FalseExit; + } int index = 0; - int num = value[0]; + uint num = TChar.CastToUInt32(value[0]); // Skip past any whitespace at the beginning. if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num)) @@ -490,9 +498,12 @@ internal static ParsingStatus TryParseBinaryIntegerStyle(ReadOnlySpan< do { index++; + if ((uint)index >= (uint)value.Length) + { goto FalseExit; - num = value[index]; + } + num = TChar.CastToUInt32(value[index]); } while (IsWhite(num)); } @@ -507,45 +518,63 @@ internal static ParsingStatus TryParseBinaryIntegerStyle(ReadOnlySpan< { isNegative = true; index++; + if ((uint)index >= (uint)value.Length) + { goto FalseExit; - num = value[index]; + } + num = TChar.CastToUInt32(value[index]); } else if (num == '+') { index++; + if ((uint)index >= (uint)value.Length) + { goto FalseExit; - num = value[index]; + } + num = TChar.CastToUInt32(value[index]); } } else if (info.AllowHyphenDuringParsing && num == '-') { isNegative = true; index++; + if ((uint)index >= (uint)value.Length) + { goto FalseExit; - num = value[index]; + } + num = TChar.CastToUInt32(value[index]); } else { value = value.Slice(index); index = 0; - string positiveSign = info.PositiveSign, negativeSign = info.NegativeSign; - if (!string.IsNullOrEmpty(positiveSign) && value.StartsWith(positiveSign)) + + ReadOnlySpan positiveSign = info.PositiveSignTChar(); + ReadOnlySpan negativeSign = info.NegativeSignTChar(); + + if (!positiveSign.IsEmpty && value.StartsWith(positiveSign)) { index += positiveSign.Length; + if ((uint)index >= (uint)value.Length) + { goto FalseExit; - num = value[index]; + } + num = TChar.CastToUInt32(value[index]); } - else if (!string.IsNullOrEmpty(negativeSign) && value.StartsWith(negativeSign)) + else if (!negativeSign.IsEmpty && value.StartsWith(negativeSign)) { isNegative = true; index += negativeSign.Length; + if ((uint)index >= (uint)value.Length) + { goto FalseExit; - num = value[index]; + } + num = TChar.CastToUInt32(value[index]); } } } @@ -561,9 +590,12 @@ internal static ParsingStatus TryParseBinaryIntegerStyle(ReadOnlySpan< do { index++; + if ((uint)index >= (uint)value.Length) + { goto DoneAtEnd; - num = value[index]; + } + num = TChar.CastToUInt32(value[index]); } while (num == '0'); if (!IsDigit(num)) @@ -579,19 +611,29 @@ internal static ParsingStatus TryParseBinaryIntegerStyle(ReadOnlySpan< // Parse most digits, up to the potential for overflow, which can't happen until after MaxDigitCount - 1 digits. answer = TInteger.CreateTruncating(num - '0'); // first digit index++; + for (int i = 0; i < TInteger.MaxDigitCount - 2; i++) // next MaxDigitCount - 2 digits can't overflow { if ((uint)index >= (uint)value.Length) { if (!TInteger.IsSigned) + { goto DoneAtEndButPotentialOverflow; + } else + { goto DoneAtEnd; + } } - num = value[index]; + + num = TChar.CastToUInt32(value[index]); + if (!IsDigit(num)) + { goto HasTrailingChars; + } index++; + answer = TInteger.MultiplyBy10(answer); answer += TInteger.CreateTruncating(num - '0'); } @@ -599,42 +641,60 @@ internal static ParsingStatus TryParseBinaryIntegerStyle(ReadOnlySpan< if ((uint)index >= (uint)value.Length) { if (!TInteger.IsSigned) + { goto DoneAtEndButPotentialOverflow; + } else + { goto DoneAtEnd; + } } - num = value[index]; + + num = TChar.CastToUInt32(value[index]); + if (!IsDigit(num)) + { goto HasTrailingChars; + } index++; + // Potential overflow now processing the MaxDigitCount digit. if (!TInteger.IsSigned) { - overflow |= (answer > TInteger.MaxValueDiv10) || (answer == TInteger.MaxValueDiv10) && (num > '5'); + overflow |= (answer > TInteger.MaxValueDiv10) || ((answer == TInteger.MaxValueDiv10) && (num > '5')); } else { overflow = answer > TInteger.MaxValueDiv10; } + answer = TInteger.MultiplyBy10(answer); answer += TInteger.CreateTruncating(num - '0'); + if (TInteger.IsSigned) { overflow |= TInteger.IsGreaterThanAsUnsigned(answer, TInteger.MaxValue + (isNegative ? TInteger.One : TInteger.Zero)); } + if ((uint)index >= (uint)value.Length) + { goto DoneAtEndButPotentialOverflow; + } // At this point, we're either overflowing or hitting a formatting error. // Format errors take precedence for compatibility. - num = value[index]; + num = TChar.CastToUInt32(value[index]); + while (IsDigit(num)) { overflow = true; index++; + if ((uint)index >= (uint)value.Length) + { goto OverflowExit; - num = value[index]; + } + num = TChar.CastToUInt32(value[index]); } goto HasTrailingChars; } @@ -645,6 +705,7 @@ internal static ParsingStatus TryParseBinaryIntegerStyle(ReadOnlySpan< { goto OverflowExit; } + DoneAtEnd: if (!TInteger.IsSigned) { @@ -655,6 +716,7 @@ internal static ParsingStatus TryParseBinaryIntegerStyle(ReadOnlySpan< result = isNegative ? -answer : answer; } ParsingStatus status = ParsingStatus.OK; + Exit: return status; @@ -662,6 +724,7 @@ internal static ParsingStatus TryParseBinaryIntegerStyle(ReadOnlySpan< result = TInteger.Zero; status = ParsingStatus.Failed; goto Exit; + OverflowExit: result = TInteger.Zero; status = ParsingStatus.Overflow; @@ -672,32 +735,44 @@ internal static ParsingStatus TryParseBinaryIntegerStyle(ReadOnlySpan< if (IsWhite(num)) { if ((styles & NumberStyles.AllowTrailingWhite) == 0) + { goto FalseExit; + } + for (index++; index < value.Length; index++) { - if (!IsWhite(value[index])) + uint ch = TChar.CastToUInt32(value[index]); + + if (!IsWhite(ch)) + { break; + } } if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; } if (!TrailingZeros(value, index)) + { goto FalseExit; - + } goto DoneAtEndButPotentialOverflow; } /// Parses limited to styles that make up NumberStyles.HexNumber. - internal static ParsingStatus TryParseBinaryIntegerHexNumberStyle(ReadOnlySpan value, NumberStyles styles, out TInteger result) - where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo => - TryParseBinaryIntegerHexOrBinaryNumberStyle>(value, styles, out result); + 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 where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo + private interface IHexOrBinaryParser + where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo { static abstract NumberStyles AllowedStyles { get; } - static abstract bool IsValidChar(int ch); - static abstract uint FromChar(int ch); + static abstract bool IsValidChar(uint ch); + static abstract uint FromChar(uint ch); static abstract uint MaxDigitValue { get; } static abstract int MaxDigitCount { get; } static abstract TInteger ShiftLeftForNextDigit(TInteger value); @@ -706,8 +781,8 @@ private interface IHexOrBinaryParser where TInteger : unmanaged, IBina private readonly struct HexParser : IHexOrBinaryParser where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo { public static NumberStyles AllowedStyles => NumberStyles.HexNumber; - public static bool IsValidChar(int ch) => HexConverter.IsHexChar(ch); - public static uint FromChar(int ch) => (uint)HexConverter.FromChar(ch); + 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; public static int MaxDigitCount => TInteger.MaxHexDigitCount; public static TInteger ShiftLeftForNextDigit(TInteger value) => TInteger.MultiplyBy16(value); @@ -716,24 +791,27 @@ private interface IHexOrBinaryParser where TInteger : unmanaged, IBina private readonly struct BinaryParser : IHexOrBinaryParser where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo { public static NumberStyles AllowedStyles => NumberStyles.BinaryNumber; - public static bool IsValidChar(int ch) => (uint)(ch - '0') <= 1; - public static uint FromChar(int ch) => (uint)(ch - '0'); + public static bool IsValidChar(uint ch) => (ch - '0') <= 1; + public static uint FromChar(uint ch) => ch - '0'; public static uint MaxDigitValue => 1; public static unsafe int MaxDigitCount => sizeof(TInteger) * 8; public static TInteger ShiftLeftForNextDigit(TInteger value) => value << 1; } - private static ParsingStatus TryParseBinaryIntegerHexOrBinaryNumberStyle(ReadOnlySpan value, NumberStyles styles, out TInteger result) + private static ParsingStatus TryParseBinaryIntegerHexOrBinaryNumberStyle(ReadOnlySpan value, NumberStyles styles, out TInteger result) + where TChar : unmanaged, IUtfChar where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo where TParser : struct, IHexOrBinaryParser { Debug.Assert((styles & ~TParser.AllowedStyles) == 0, $"Only handles subsets of {TParser.AllowedStyles} format"); if (value.IsEmpty) + { goto FalseExit; + } int index = 0; - int num = value[0]; + uint num = TChar.CastToUInt32(value[0]); // Skip past any whitespace at the beginning. if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num)) @@ -741,9 +819,12 @@ private static ParsingStatus TryParseBinaryIntegerHexOrBinaryNumberStyle= (uint)value.Length) + { goto FalseExit; - num = value[index]; + } + num = TChar.CastToUInt32(value[index]); } while (IsWhite(num)); } @@ -759,47 +840,70 @@ private static ParsingStatus TryParseBinaryIntegerHexOrBinaryNumberStyle= (uint)value.Length) + { goto DoneAtEnd; - num = value[index]; + } + num = TChar.CastToUInt32(value[index]); } while (num == '0'); + if (!TParser.IsValidChar(num)) + { goto HasTrailingChars; + } } // Parse up through MaxDigitCount digits, as no overflow is possible answer = TInteger.CreateTruncating(TParser.FromChar(num)); // first digit index++; + for (int i = 0; i < TParser.MaxDigitCount - 1; i++) // next MaxDigitCount - 1 digits can't overflow { if ((uint)index >= (uint)value.Length) + { goto DoneAtEnd; - num = value[index]; + } + num = TChar.CastToUInt32(value[index]); uint numValue = TParser.FromChar(num); + if (numValue > TParser.MaxDigitValue) + { goto HasTrailingChars; + } index++; + answer = TParser.ShiftLeftForNextDigit(answer); answer += TInteger.CreateTruncating(numValue); } // If there's another digit, it's an overflow. if ((uint)index >= (uint)value.Length) + { goto DoneAtEnd; - num = value[index]; + } + + num = TChar.CastToUInt32(value[index]); + if (!TParser.IsValidChar(num)) + { goto HasTrailingChars; + } // At this point, we're either overflowing or hitting a formatting error. // Format errors take precedence for compatibility. Read through any remaining digits. do { index++; + if ((uint)index >= (uint)value.Length) + { goto OverflowExit; - num = value[index]; + } + num = TChar.CastToUInt32(value[index]); } while (TParser.IsValidChar(num)); + overflow = true; goto HasTrailingChars; } @@ -810,9 +914,11 @@ private static ParsingStatus TryParseBinaryIntegerHexOrBinaryNumberStyle= (uint)value.Length) + { goto DoneAtEndButPotentialOverflow; + } } if (!TrailingZeros(value, index)) + { goto FalseExit; - + } goto DoneAtEndButPotentialOverflow; } @@ -851,7 +969,11 @@ internal static decimal ParseDecimal(ReadOnlySpan value, NumberStyles styl ParsingStatus status = TryParseDecimal(value, styles, info, out decimal result); if (status != ParsingStatus.OK) { - ThrowOverflowOrFormatException(status, value, TypeCode.Decimal); + if (status == ParsingStatus.Failed) + { + ThrowFormatException(value); + } + ThrowOverflowException(SR.Overflow_Decimal); } return result; @@ -904,9 +1026,9 @@ internal static unsafe bool TryNumberToDecimal(ref NumberBuffer number, ref deci { // multiply by 10 ulong tmpLow = (uint)low64 * 10UL; - ulong tmp64 = (uint)(low64 >> 32) * 10UL + (tmpLow >> 32); + ulong tmp64 = ((uint)(low64 >> 32) * 10UL) + (tmpLow >> 32); low64 = (uint)tmpLow + (tmp64 << 32); - high = (uint)(tmp64 >> 32) + high * 10; + high = (uint)(tmp64 >> 32) + (high * 10); if (c != 0) { @@ -937,7 +1059,7 @@ internal static unsafe bool TryNumberToDecimal(ref NumberBuffer number, ref deci while ((c != 0) && hasZeroTail) { - hasZeroTail &= (c == '0'); + hasZeroTail &= c == '0'; c = *++p; } @@ -977,18 +1099,18 @@ internal static unsafe bool TryNumberToDecimal(ref NumberBuffer number, ref deci return true; } - internal static TFloat ParseFloat(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) + internal static TFloat ParseFloat(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) + where TChar : unmanaged, IUtfChar where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo { if (!TryParseFloat(value, styles, info, out TFloat result)) { - ThrowOverflowOrFormatException(ParsingStatus.Failed, value); + ThrowFormatException(value); } - return result; } - internal static unsafe ParsingStatus TryParseDecimal(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out decimal result) + internal static ParsingStatus TryParseDecimal(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out decimal result) { NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, stackalloc byte[DecimalNumberBufferLength]); @@ -1007,76 +1129,162 @@ internal static unsafe ParsingStatus TryParseDecimal(ReadOnlySpan value, N return ParsingStatus.OK; } - internal static bool SpanStartsWith(ReadOnlySpan span, char c) => !span.IsEmpty && span[0] == c; + internal static bool SpanStartsWith(ReadOnlySpan span, TChar c) + where TChar : unmanaged, IUtfChar + { + return !span.IsEmpty && (span[0] == c); + } + + internal static bool SpanStartsWith(ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) + where TChar : unmanaged, IUtfChar + { + if (typeof(TChar) == typeof(char)) + { + ReadOnlySpan typedSpan = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length); + ReadOnlySpan typedValue = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref MemoryMarshal.GetReference(value)), value.Length); + return typedSpan.StartsWith(typedValue, comparisonType); + } + else + { + Debug.Assert(typeof(TChar) == typeof(byte)); + + ReadOnlySpan typedSpan = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length); + ReadOnlySpan typedValue = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref MemoryMarshal.GetReference(value)), value.Length); + return typedSpan.StartsWithUtf8(typedValue, comparisonType); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ReadOnlySpan SpanTrim(ReadOnlySpan span) + where TChar : unmanaged, IUtfChar + { + if (typeof(TChar) == typeof(char)) + { + ReadOnlySpan typedSpan = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length); + ReadOnlySpan result = typedSpan.Trim(); + return MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref MemoryMarshal.GetReference(result)), result.Length); + } + else + { + Debug.Assert(typeof(TChar) == typeof(byte)); + + ReadOnlySpan typedSpan = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length); + ReadOnlySpan result = typedSpan.TrimUtf8(); + return MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref MemoryMarshal.GetReference(result)), result.Length); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool SpanEqualsOrdinalIgnoreCase(ReadOnlySpan span, ReadOnlySpan value) + where TChar : unmanaged, IUtfChar + { + if (typeof(TChar) == typeof(char)) + { + ReadOnlySpan typedSpan = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length); + ReadOnlySpan typedValue = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref MemoryMarshal.GetReference(value)), value.Length); + return typedSpan.EqualsOrdinalIgnoreCase(typedValue); + } + else + { + Debug.Assert(typeof(TChar) == typeof(byte)); + + ReadOnlySpan typedSpan = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length); + ReadOnlySpan typedValue = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref MemoryMarshal.GetReference(value)), value.Length); + return typedSpan.EqualsOrdinalIgnoreCaseUtf8(typedValue); + } + } - internal static unsafe bool TryParseFloat(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out TFloat result) + internal static bool TryParseFloat(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out TFloat result) + where TChar: unmanaged, IUtfChar where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo { NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, stackalloc byte[TFloat.NumberBufferLength]); if (!TryStringToNumber(value, styles, ref number, info)) { - ReadOnlySpan valueTrim = value.Trim(); + 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. - if (valueTrim.EqualsOrdinalIgnoreCase(info.PositiveInfinitySymbol)) + ReadOnlySpan positiveInfinitySymbol = info.PositiveInfinitySymbolTChar(); + + if (SpanEqualsOrdinalIgnoreCase(valueTrim, positiveInfinitySymbol)) { result = TFloat.PositiveInfinity; + return true; } - else if (valueTrim.EqualsOrdinalIgnoreCase(info.NegativeInfinitySymbol)) + + if (SpanEqualsOrdinalIgnoreCase(valueTrim, info.NegativeInfinitySymbolTChar())) { result = TFloat.NegativeInfinity; + return true; } - else if (valueTrim.EqualsOrdinalIgnoreCase(info.NaNSymbol)) + + ReadOnlySpan nanSymbol = info.NaNSymbolTChar(); + + if (SpanEqualsOrdinalIgnoreCase(valueTrim, nanSymbol)) { result = TFloat.NaN; + return true; } - else if (valueTrim.StartsWith(info.PositiveSign, StringComparison.OrdinalIgnoreCase)) + + var positiveSign = info.PositiveSignTChar(); + + if (SpanStartsWith(valueTrim, positiveSign, StringComparison.OrdinalIgnoreCase)) { - valueTrim = valueTrim.Slice(info.PositiveSign.Length); + valueTrim = valueTrim.Slice(positiveSign.Length); - if (valueTrim.EqualsOrdinalIgnoreCase(info.PositiveInfinitySymbol)) + if (SpanEqualsOrdinalIgnoreCase(valueTrim, positiveInfinitySymbol)) { result = TFloat.PositiveInfinity; + return true; } - else if (valueTrim.EqualsOrdinalIgnoreCase(info.NaNSymbol)) + else if (SpanEqualsOrdinalIgnoreCase(valueTrim, nanSymbol)) { result = TFloat.NaN; + return true; } - else - { - result = TFloat.Zero; - return false; - } - } - else if ((valueTrim.StartsWith(info.NegativeSign, StringComparison.OrdinalIgnoreCase) && valueTrim.Slice(info.NegativeSign.Length).EqualsOrdinalIgnoreCase(info.NaNSymbol)) || - (info.AllowHyphenDuringParsing && SpanStartsWith(valueTrim, '-') && valueTrim.Slice(1).EqualsOrdinalIgnoreCase(info.NaNSymbol))) - { - result = TFloat.NaN; + + result = TFloat.Zero; + return false; } - else + + ReadOnlySpan negativeSign = info.NegativeSignTChar(); + + if (SpanStartsWith(valueTrim, negativeSign, StringComparison.OrdinalIgnoreCase)) { - result = TFloat.Zero; - return false; // We really failed + 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; + } } - } - else - { - result = NumberToFloat(ref number); + + result = TFloat.Zero; + return false; // We really failed } + result = NumberToFloat(ref number); return true; } - internal static unsafe bool TryStringToNumber(ReadOnlySpan value, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info) + internal static unsafe bool TryStringToNumber(ReadOnlySpan value, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info) + where TChar : unmanaged, IUtfChar { Debug.Assert(info != null); - fixed (char* stringPointer = &MemoryMarshal.GetReference(value)) + + fixed (TChar* stringPointer = &MemoryMarshal.GetReference(value)) { - char* p = stringPointer; + TChar* p = stringPointer; + if (!TryParseNumber(ref p, p + value.Length, styles, ref number, info) || ((int)(p - stringPointer) < value.Length && !TrailingZeros(value, (int)(p - stringPointer)))) { @@ -1090,17 +1298,22 @@ internal static unsafe bool TryStringToNumber(ReadOnlySpan value, NumberSt } [MethodImpl(MethodImplOptions.NoInlining)] // rare slow path that shouldn't impact perf of the main use case - private static bool TrailingZeros(ReadOnlySpan value, int index) => + private static bool TrailingZeros(ReadOnlySpan value, int index) + where TChar : unmanaged, IUtfChar + { // For compatibility, we need to allow trailing zeros at the end of a number string - value.Slice(index).IndexOfAnyExcept('\0') < 0; + return value.Slice(index).IndexOfAnyExcept(TChar.CastFrom('\0')) < 0; + } - private static bool IsSpaceReplacingChar(char c) => c == '\u00a0' || c == '\u202f'; + private static bool IsSpaceReplacingChar(uint c) => (c == '\u00a0') || (c == '\u202f'); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe char* MatchNegativeSignChars(char* p, char* pEnd, NumberFormatInfo info) + private static unsafe TChar* MatchNegativeSignChars(TChar* p, TChar* pEnd, NumberFormatInfo info) + where TChar : unmanaged, IUtfChar { - char* ret = MatchChars(p, pEnd, info.NegativeSign); - if (ret == null && info.AllowHyphenDuringParsing && p < pEnd && *p == '-') + TChar* ret = MatchChars(p, pEnd, info.NegativeSignTChar()); + + if ((ret is null) && info.AllowHyphenDuringParsing && (p < pEnd) && (TChar.CastToUInt32(*p) == '-')) { ret = p + 1; } @@ -1108,28 +1321,37 @@ private static bool TrailingZeros(ReadOnlySpan value, int index) => return ret; } - private static unsafe char* MatchChars(char* p, char* pEnd, string value) + private static unsafe TChar* MatchChars(TChar* p, TChar* pEnd, ReadOnlySpan value) + where TChar : unmanaged, IUtfChar { - Debug.Assert(p != null && pEnd != null && p <= pEnd && value != null); - fixed (char* stringPointer = value) + Debug.Assert((p != null) && (pEnd != null) && (p <= pEnd) && (value != null)); + + fixed (TChar* stringPointer = value) { - char* str = stringPointer; - if (*str != '\0') + TChar* str = stringPointer; + + if (TChar.CastToUInt32(*str) != '\0') { // We only hurt the failure case // This fix is for French or Kazakh cultures. Since a user cannot type 0xA0 or 0x202F as a // space character we use 0x20 space character instead to mean the same. while (true) { - char cp = p < pEnd ? *p : '\0'; - if (cp != *str && !(IsSpaceReplacingChar(*str) && cp == '\u0020')) + uint cp = (p < pEnd) ? TChar.CastToUInt32(*p) : '\0'; + uint val = TChar.CastToUInt32(*str); + + if ((cp != val) && !(IsSpaceReplacingChar(val) && (cp == '\u0020'))) { break; } + p++; str++; - if (*str == '\0') + + if (val == '\0') + { return p; + } } } } @@ -1137,9 +1359,9 @@ private static bool TrailingZeros(ReadOnlySpan value, int index) => return null; } - private static bool IsWhite(int ch) => ch == 0x20 || (uint)(ch - 0x09) <= (0x0D - 0x09); + private static bool IsWhite(uint ch) => (ch == 0x20) || ((ch - 0x09) <= (0x0D - 0x09)); - private static bool IsDigit(int ch) => ((uint)ch - '0') <= 9; + private static bool IsDigit(uint ch) => (ch - '0') <= 9; internal enum ParsingStatus { @@ -1149,88 +1371,37 @@ internal enum ParsingStatus } [DoesNotReturn] - internal static void ThrowOverflowOrFormatException(ParsingStatus status, ReadOnlySpan value, TypeCode type = 0) => throw GetException(status, value, type); - - [DoesNotReturn] - internal static void ThrowOverflowOrFormatException(ParsingStatus status, ReadOnlySpan value) + internal static void ThrowOverflowOrFormatException(ParsingStatus status, ReadOnlySpan value) + where TChar : unmanaged, IUtfChar where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo { - throw GetException(status, value); + if (status == ParsingStatus.Failed) + { + ThrowFormatException(value); + } + ThrowOverflowException(); } [DoesNotReturn] - internal static void ThrowOverflowException(TypeCode type) => throw GetOverflowException(type); - - [DoesNotReturn] - internal static void ThrowOverflowOrFormatExceptionInt128(ParsingStatus status) => throw GetExceptionInt128(status); - - [DoesNotReturn] - internal static void ThrowOverflowOrFormatExceptionUInt128(ParsingStatus status) => throw GetExceptionUInt128(status); - - private static Exception GetException(ParsingStatus status, ReadOnlySpan value, TypeCode type) + internal static void ThrowFormatException(ReadOnlySpan value) + where TChar : unmanaged, IUtfChar { - if (status == ParsingStatus.Failed) - return new FormatException(SR.Format(SR.Format_InvalidStringWithValue, value.ToString())); - - return GetOverflowException(type); + throw new FormatException(SR.Format(SR.Format_InvalidStringWithValue, value.ToString())); } - private static Exception GetException(ParsingStatus status, ReadOnlySpan value) + [DoesNotReturn] + internal static void ThrowOverflowException() where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo { - if (status == ParsingStatus.Failed) - return new FormatException(SR.Format(SR.Format_InvalidStringWithValue, value.ToString())); - - return new OverflowException(TInteger.OverflowMessage); + throw new OverflowException(TInteger.OverflowMessage); } - private static OverflowException GetOverflowException(TypeCode type) + [DoesNotReturn] + internal static void ThrowOverflowException(string message) { - string s; - switch (type) - { - case TypeCode.SByte: - s = SR.Overflow_SByte; - break; - case TypeCode.Byte: - s = SR.Overflow_Byte; - break; - case TypeCode.Int16: - s = SR.Overflow_Int16; - break; - case TypeCode.UInt16: - s = SR.Overflow_UInt16; - break; - case TypeCode.Int32: - s = SR.Overflow_Int32; - break; - case TypeCode.UInt32: - s = SR.Overflow_UInt32; - break; - case TypeCode.Int64: - s = SR.Overflow_Int64; - break; - case TypeCode.UInt64: - s = SR.Overflow_UInt64; - break; - default: - Debug.Assert(type == TypeCode.Decimal); - s = SR.Overflow_Decimal; - break; - } - return new OverflowException(s); + throw new OverflowException(message); } - private static Exception GetExceptionInt128(ParsingStatus status) => - status == ParsingStatus.Failed ? - new FormatException(SR.Format_InvalidString) : - new OverflowException(SR.Overflow_Int128); - - private static Exception GetExceptionUInt128(ParsingStatus status) => - status == ParsingStatus.Failed ? - new FormatException(SR.Format_InvalidString) : - new OverflowException(SR.Overflow_UInt128); - internal static TFloat NumberToFloat(ref NumberBuffer number) where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo { diff --git a/src/libraries/System.Private.CoreLib/src/System/ParseNumbers.cs b/src/libraries/System.Private.CoreLib/src/System/ParseNumbers.cs index 76c4f52cb8719..37eb86964a266 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ParseNumbers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ParseNumbers.cs @@ -91,7 +91,7 @@ public static long StringToLong(ReadOnlySpan s, int radix, int flags, ref // Return the value properly signed. if ((ulong)result == 0x8000000000000000 && sign == 1 && r == 10 && ((flags & TreatAsUnsigned) == 0)) - Number.ThrowOverflowException(TypeCode.Int64); + Number.ThrowOverflowException(); if (r == 10) { @@ -182,16 +182,16 @@ public static int StringToInt(ReadOnlySpan s, int radix, int flags, ref in if ((flags & TreatAsI1) != 0) { if ((uint)result > 0xFF) - Number.ThrowOverflowException(TypeCode.SByte); + Number.ThrowOverflowException(); } else if ((flags & TreatAsI2) != 0) { if ((uint)result > 0xFFFF) - Number.ThrowOverflowException(TypeCode.Int16); + Number.ThrowOverflowException(); } else if ((uint)result == 0x80000000 && sign == 1 && r == 10 && ((flags & TreatAsUnsigned) == 0)) { - Number.ThrowOverflowException(TypeCode.Int32); + Number.ThrowOverflowException(); } if (r == 10) @@ -225,7 +225,7 @@ private static long GrabLongs(int radix, ReadOnlySpan s, ref int i, bool i // Check for overflows - this is sufficient & correct. if (result > maxVal || ((long)result) < 0) { - Number.ThrowOverflowException(TypeCode.Int64); + Number.ThrowOverflowException(); } result = result * (ulong)radix + (ulong)value; @@ -234,7 +234,7 @@ private static long GrabLongs(int radix, ReadOnlySpan s, ref int i, bool i if ((long)result < 0 && result != 0x8000000000000000) { - Number.ThrowOverflowException(TypeCode.Int64); + Number.ThrowOverflowException(); } } else @@ -252,14 +252,14 @@ private static long GrabLongs(int radix, ReadOnlySpan s, ref int i, bool i // Check for overflows - this is sufficient & correct. if (result > maxVal) { - Number.ThrowOverflowException(TypeCode.UInt64); + Number.ThrowOverflowException(); } ulong temp = result * (ulong)radix + (ulong)value; if (temp < result) // this means overflow as well { - Number.ThrowOverflowException(TypeCode.UInt64); + Number.ThrowOverflowException(); } result = temp; @@ -286,14 +286,14 @@ private static int GrabInts(int radix, ReadOnlySpan s, ref int i, bool isU // Check for overflows - this is sufficient & correct. if (result > maxVal || (int)result < 0) { - Number.ThrowOverflowException(TypeCode.Int32); + Number.ThrowOverflowException(); } result = result * (uint)radix + (uint)value; i++; } if ((int)result < 0 && result != 0x80000000) { - Number.ThrowOverflowException(TypeCode.Int32); + Number.ThrowOverflowException(); } } else @@ -311,14 +311,14 @@ private static int GrabInts(int radix, ReadOnlySpan s, ref int i, bool isU // Check for overflows - this is sufficient & correct. if (result > maxVal) { - Number.ThrowOverflowException(TypeCode.UInt32); + Number.ThrowOverflowException(); } uint temp = result * (uint)radix + (uint)value; if (temp < result) // this means overflow as well { - Number.ThrowOverflowException(TypeCode.UInt32); + Number.ThrowOverflowException(); } result = temp; diff --git a/src/libraries/System.Private.CoreLib/src/System/SByte.cs b/src/libraries/System.Private.CoreLib/src/System/SByte.cs index 9f09ec5318ad6..b134a5d72ada8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SByte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SByte.cs @@ -144,7 +144,7 @@ public static sbyte Parse(string s, NumberStyles style, IFormatProvider? provide public static sbyte Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.ParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider)); + return Number.ParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider)); } public static bool TryParse([NotNullWhen(true)] string? s, out sbyte result) => TryParse(s, NumberStyles.Integer, provider: null, out result); @@ -160,7 +160,7 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I result = 0; return false; } - return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out sbyte result) diff --git a/src/libraries/System.Private.CoreLib/src/System/Single.cs b/src/libraries/System.Private.CoreLib/src/System/Single.cs index f6c5b2e6fe057..a3789b906eb16 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Single.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Single.cs @@ -376,53 +376,30 @@ public bool TryFormat(Span utf8Destination, out int bytesWritten, [StringS // PositiveInfinity or NegativeInfinity for a number that is too // large or too small. // - public static float Parse(string s) - { - if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseFloat(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo); - } + public static float Parse(string s) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null); - public static float Parse(string s, NumberStyles style) - { - NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseFloat(s, style, NumberFormatInfo.CurrentInfo); - } + public static float Parse(string s, NumberStyles style) => Parse(s, style, provider: null); - public static float Parse(string s, IFormatProvider? provider) - { - if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseFloat(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.GetInstance(provider)); - } + public static float Parse(string s, IFormatProvider? provider) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider); public static float Parse(string s, NumberStyles style, IFormatProvider? provider) { - NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseFloat(s, style, NumberFormatInfo.GetInstance(provider)); + if (s is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); + } + return Parse(s.AsSpan(), style, provider); } public static float Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Float | NumberStyles.AllowThousands, IFormatProvider? provider = null) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - return Number.ParseFloat(s, style, NumberFormatInfo.GetInstance(provider)); + return Number.ParseFloat(s, style, NumberFormatInfo.GetInstance(provider)); } - public static bool TryParse([NotNullWhen(true)] string? s, out float result) - { - if (s == null) - { - result = 0; - return false; - } - - return TryParse((ReadOnlySpan)s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo, out result); - } + public static bool TryParse([NotNullWhen(true)] string? s, out float result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null, out result); - public static bool TryParse(ReadOnlySpan s, out float result) - { - return TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo, out result); - } + public static bool TryParse(ReadOnlySpan s, out float result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null, out result); public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out float result) { @@ -433,19 +410,13 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I result = 0; return false; } - - return TryParse((ReadOnlySpan)s, style, NumberFormatInfo.GetInstance(provider), out result); + return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result); } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out float result) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - return TryParse(s, style, NumberFormatInfo.GetInstance(provider), out result); - } - - private static bool TryParse(ReadOnlySpan s, NumberStyles style, NumberFormatInfo info, out float result) - { - return Number.TryParseFloat(s, style, info, out result); + return Number.TryParseFloat(s, style, NumberFormatInfo.GetInstance(provider), out result); } // diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/Unicode/Utf8Utility.cs b/src/libraries/System.Private.CoreLib/src/System/Text/Unicode/Utf8Utility.cs index e23b416f1b517..9708c32e49e24 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/Unicode/Utf8Utility.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/Unicode/Utf8Utility.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; namespace System.Text.Unicode { @@ -162,5 +163,130 @@ internal static ulong ConvertAllAsciiBytesInUInt64ToLowercase(ulong value) return value ^ mask; // bit flip uppercase letters [A-Z] => [a-z] } + + /// + /// Given two UInt32s that represent four ASCII UTF-8 characters each, returns true iff + /// the two inputs are equal using an ordinal case-insensitive comparison. + /// + /// + /// This is a branchless implementation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool UInt32OrdinalIgnoreCaseAscii(uint valueA, uint valueB) + { + // Not currently intrinsified in mono interpreter, the UTF16 version is + // ASSUMPTION: Caller has validated that input values are ASCII. + Debug.Assert(AllBytesInUInt32AreAscii(valueA)); + Debug.Assert(AllBytesInUInt32AreAscii(valueB)); + + // The logic here is very simple and is doing SIMD Within A Register (SWAR) + // + // First we want to create a mask finding the upper-case ASCII characters + // + // To do that, we can take the above presumption that all characters are ASCII + // and therefore between 0x00 and 0x7F, inclusive. This means that `0x80 + char` + // will never overflow and will at most produce 0xFF. + // + // Given that, we can check if a byte is greater than a value by adding it to + // 0x80 and then subtracting the constant we're comparing against. So, for example, + // if we want to find all characters greater than 'A' we do `value + 0x80 - 'A'`. + // + // Given that 'A' is 0x41, we end up with `0x41 + 0x80 == 0xC1` then we subtract 'A' + // giving us `0xC1 - 0x41 == 0x80` and up to `0xBE` for 'DEL' (0x7F). This means that + // any character greater than or equal to 'A' will have the most significant bit set. + // + // This can itself be simplified down to `val + (0x80 - 'A')` or `val + 0x3F` + // + // We also want to find the characters less than or equal to 'Z' as well. This follows + // the same general principle but relies on finding the inverse instead. That is, we + // want to find all characters greater than or equal to ('Z' + 1) and then inverse it. + // + // To confirm this, lets look at 'Z' which has the value of '0x5A'. So we first do + // `0x5A + 0x80 == 0xDA`, then we subtract `[' (0x5B) giving us `0xDA - 0x5B == 0x80`. + // This means that any character greater than 'Z' will now have the most significant bit set. + // + // It then follows that taking the ones complement will give us a mask representing the bytes + // which are less than or equal to 'Z' since `!(val >= 0x5B) == (val <= 0x5A)` + // + // This then gives us that `('A' <= val) && (val <= 'Z')` is representable as + // `(val + 0x3F) & ~(val + 0x25)` + // + // However, since a `val` cannot be simultaneously less than 'A' and greater than 'Z' we + // are able to simplify this further to being just `(val + 0x3F) ^ (val + 0x25)` + // + // We then want to mask off the excess bits that aren't important to the mask and right + // shift by two. This gives us `0x20` for a byte which is an upper-case ASCII character + // and `0x00` otherwise. + // + // We now have a super efficient implementation that does a correct comparison in + // 12 instructions and with zero branching. + + uint letterMaskA = (((valueA + 0x3F3F3F3F) ^ (valueA + 0x25252525)) & 0x80808080) >> 2; + uint letterMaskB = (((valueB + 0x3F3F3F3F) ^ (valueB + 0x25252525)) & 0x80808080) >> 2; + + return (valueA | letterMaskA) == (valueB | letterMaskB); + } + + /// + /// Given two UInt64s that represent eight ASCII UTF-8 characters each, returns true iff + /// the two inputs are equal using an ordinal case-insensitive comparison. + /// + /// + /// This is a branchless implementation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool UInt64OrdinalIgnoreCaseAscii(ulong valueA, ulong valueB) + { + // Not currently intrinsified in mono interpreter, the UTF16 version is + // ASSUMPTION: Caller has validated that input values are ASCII. + Debug.Assert(AllBytesInUInt64AreAscii(valueA)); + Debug.Assert(AllBytesInUInt64AreAscii(valueB)); + + // Duplicate of logic in UInt32OrdinalIgnoreCaseAscii, but using 64-bit consts. + // See comments in that method for more info. + + ulong letterMaskA = (((valueA + 0x3F3F3F3F3F3F3F3F) ^ (valueA + 0x2525252525252525)) & 0x8080808080808080) >> 2; + ulong letterMaskB = (((valueB + 0x3F3F3F3F3F3F3F3F) ^ (valueB + 0x2525252525252525)) & 0x8080808080808080) >> 2; + + return (valueA | letterMaskA) == (valueB | letterMaskB); + } + + /// + /// Returns true iff the Vector128 represents 16 ASCII UTF-8 characters in machine endianness. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool AllBytesInVector128AreAscii(Vector128 vec) + { + return (vec & Vector128.Create(unchecked((byte)(~0x7F)))) == Vector128.Zero; + } + + /// + /// Given two Vector128 that represent 16 ASCII UTF-8 characters each, returns true iff + /// the two inputs are equal using an ordinal case-insensitive comparison. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool Vector128OrdinalIgnoreCaseAscii(Vector128 vec1, Vector128 vec2) + { + // ASSUMPTION: Caller has validated that input values are ASCII. + + // the 0x80 bit of each word of 'lowerIndicator' will be set iff the word has value >= 'A' + Vector128 lowIndicator1 = Vector128.Create((sbyte)(0x80 - 'A')) + vec1.AsSByte(); + Vector128 lowIndicator2 = Vector128.Create((sbyte)(0x80 - 'A')) + vec2.AsSByte(); + + // the 0x80 bit of each word of 'combinedIndicator' will be set iff the word has value >= 'A' and <= 'Z' + Vector128 combIndicator1 = + Vector128.LessThan(Vector128.Create(unchecked((sbyte)(('Z' - 'A') - 0x80))), lowIndicator1); + Vector128 combIndicator2 = + Vector128.LessThan(Vector128.Create(unchecked((sbyte)(('Z' - 'A') - 0x80))), lowIndicator2); + + // Convert both vectors to lower case by adding 0x20 bit for all [A-Z][a-z] characters + Vector128 lcVec1 = + Vector128.AndNot(Vector128.Create((sbyte)0x20), combIndicator1) + vec1.AsSByte(); + Vector128 lcVec2 = + Vector128.AndNot(Vector128.Create((sbyte)0x20), combIndicator2) + vec2.AsSByte(); + + // Compare two lowercased vectors + return (lcVec1 ^ lcVec2) == Vector128.Zero; + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt128.cs b/src/libraries/System.Private.CoreLib/src/System/UInt128.cs index 85ff1a912a4c4..90983735b11f0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt128.cs @@ -142,7 +142,7 @@ public static UInt128 Parse(string s, NumberStyles style, IFormatProvider? provi public static UInt128 Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.ParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider)); + return Number.ParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider)); } public static bool TryParse([NotNullWhen(true)] string? s, out UInt128 result) => TryParse(s, NumberStyles.Integer, provider: null, out result); @@ -158,7 +158,7 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I result = 0; return false; } - return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out UInt128 result) @@ -217,7 +217,7 @@ public static explicit operator decimal(UInt128 value) if (value._upper > uint.MaxValue) { // The default behavior of decimal conversions is to always throw on overflow - Number.ThrowOverflowException(TypeCode.Decimal); + Number.ThrowOverflowException(SR.Overflow_Decimal); } uint hi32 = (uint)(value._upper); diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt16.cs b/src/libraries/System.Private.CoreLib/src/System/UInt16.cs index 54f87f1264cea..f3addefb2e41b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt16.cs @@ -136,7 +136,7 @@ public static ushort Parse(string s, NumberStyles style, IFormatProvider? provid public static ushort Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.ParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider)); + return Number.ParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider)); } public static bool TryParse([NotNullWhen(true)] string? s, out ushort result) => TryParse(s, NumberStyles.Integer, provider: null, out result); @@ -152,7 +152,7 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I result = 0; return false; } - return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out ushort result) diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt32.cs b/src/libraries/System.Private.CoreLib/src/System/UInt32.cs index dcec51866930d..bcd54ea45a94d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt32.cs @@ -146,7 +146,7 @@ public static uint Parse(string s, NumberStyles style, IFormatProvider? provider public static uint Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.ParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider)); + return Number.ParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider)); } public static bool TryParse([NotNullWhen(true)] string? s, out uint result) => TryParse(s, NumberStyles.Integer, provider: null, out result); @@ -162,7 +162,7 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I result = 0; return false; } - return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out uint result) diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt64.cs b/src/libraries/System.Private.CoreLib/src/System/UInt64.cs index c9b2e057480d7..edb8becbc8e50 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt64.cs @@ -145,7 +145,7 @@ public static ulong Parse(string s, NumberStyles style, IFormatProvider? provide public static ulong Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.ParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider)); + return Number.ParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider)); } public static bool TryParse([NotNullWhen(true)] string? s, out ulong result) => TryParse(s, NumberStyles.Integer, provider: null, out result); @@ -161,7 +161,7 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I result = 0; return false; } - return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out ulong result) From fe438cab62e1842d16bce193ac3daa303e1f0ad1 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 29 May 2023 11:22:24 -0700 Subject: [PATCH 05/19] Updating the primitive numeric types to include UTF-8 parsing support --- .../System.Private.CoreLib/src/System/Byte.cs | 30 +++++++ .../src/System/Decimal.cs | 74 +++++++++-------- .../src/System/Double.cs | 30 +++++++ .../System.Private.CoreLib/src/System/Half.cs | 30 +++++++ .../src/System/Int128.cs | 30 +++++++ .../src/System/Int16.cs | 30 +++++++ .../src/System/Int32.cs | 30 +++++++ .../src/System/Int64.cs | 30 +++++++ .../src/System/IntPtr.cs | 34 ++++++++ .../src/System/Number.Parsing.cs | 6 +- .../System/Runtime/InteropServices/NFloat.cs | 36 ++++++++- .../src/System/SByte.cs | 30 +++++++ .../src/System/Single.cs | 30 +++++++ .../src/System/UInt128.cs | 30 +++++++ .../src/System/UInt16.cs | 30 +++++++ .../src/System/UInt32.cs | 30 +++++++ .../src/System/UInt64.cs | 30 +++++++ .../src/System/UIntPtr.cs | 34 ++++++++ .../ref/System.Runtime.InteropServices.cs | 5 ++ .../System.Runtime/ref/System.Runtime.cs | 80 +++++++++++++++++++ 20 files changed, 622 insertions(+), 37 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Byte.cs b/src/libraries/System.Private.CoreLib/src/System/Byte.cs index 0c02ae4171286..ac7bf3c6d6a38 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Byte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Byte.cs @@ -115,6 +115,12 @@ public static byte Parse(ReadOnlySpan s, NumberStyles style = NumberStyles public static bool TryParse(ReadOnlySpan s, out byte result) => TryParse(s, NumberStyles.Integer, provider: null, out result); + /// Tries to convert a UTF-8 character span containing the string representation of a number to its 8-bit unsigned integer equivalent. + /// A span containing the UTF-8 characters representing the number to convert. + /// When this method returns, contains the 8-bit unsigned integer value equivalent to the number contained in if the conversion succeeded, or zero if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten. + /// true if was converted successfully; otherwise, false. + public static bool TryParse(ReadOnlySpan utf8Text, out byte result) => TryParse(utf8Text, NumberStyles.Integer, provider: null, out result); + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out byte result) { NumberFormatInfo.ValidateParseStyleInteger(style); @@ -1151,6 +1157,30 @@ static bool INumberBase.TryConvertToTruncating(byte value, [MaybeN /// static byte IUnaryPlusOperators.operator +(byte value) => (byte)(+value); + // + // IUtf8SpanParsable + // + + /// + public static byte Parse(ReadOnlySpan utf8Text, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.ParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider)); + } + + /// + 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; + } + + /// + public static byte Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) => Parse(utf8Text, NumberStyles.Integer, provider); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out byte result) => TryParse(utf8Text, NumberStyles.Integer, provider, out result); + // // IUtfChar // diff --git a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs index 1d487431ba226..8972292fe6700 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs @@ -517,30 +517,19 @@ public bool TryFormat(Span utf8Destination, out int bytesWritten, [StringS // Parse also allows a currency symbol, a trailing negative sign, and // parentheses in the number. // - public static decimal Parse(string s) - { - if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseDecimal(s, NumberStyles.Number, NumberFormatInfo.CurrentInfo); - } + public static decimal Parse(string s) => Parse(s, NumberStyles.Number, provider: null); - public static decimal Parse(string s, NumberStyles style) - { - NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseDecimal(s, style, NumberFormatInfo.CurrentInfo); - } + public static decimal Parse(string s, NumberStyles style) => Parse(s, style, provider: null); - public static decimal Parse(string s, IFormatProvider? provider) - { - if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseDecimal(s, NumberStyles.Number, NumberFormatInfo.GetInstance(provider)); - } + public static decimal Parse(string s, IFormatProvider? provider) => Parse(s, NumberStyles.Number, provider); public static decimal Parse(string s, NumberStyles style, IFormatProvider? provider) { - NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); - return Number.ParseDecimal(s, style, NumberFormatInfo.GetInstance(provider)); + if (s is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); + } + return Parse(s.AsSpan(), style, provider); } public static decimal Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Number, IFormatProvider? provider = null) @@ -549,21 +538,15 @@ public static decimal Parse(ReadOnlySpan s, NumberStyles style = NumberSty return Number.ParseDecimal(s, style, NumberFormatInfo.GetInstance(provider)); } - public static bool TryParse([NotNullWhen(true)] string? s, out decimal result) - { - if (s == null) - { - result = 0; - return false; - } + public static bool TryParse([NotNullWhen(true)] string? s, out decimal result) => TryParse(s, NumberStyles.Number, provider: null, out result); - return Number.TryParseDecimal(s, NumberStyles.Number, NumberFormatInfo.CurrentInfo, out result) == Number.ParsingStatus.OK; - } + public static bool TryParse(ReadOnlySpan s, out decimal result) => TryParse(s, NumberStyles.Number, provider: null, out result); - public static bool TryParse(ReadOnlySpan s, out decimal result) - { - return Number.TryParseDecimal(s, NumberStyles.Number, NumberFormatInfo.CurrentInfo, out result) == Number.ParsingStatus.OK; - } + /// Tries to convert a UTF-8 character span containing the string representation of a number to its signed decimal equivalent. + /// A span containing the UTF-8 characters representing the number to convert. + /// When this method returns, contains the signed decimal value equivalent to the number contained in if the conversion succeeded, or zero if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten. + /// true if was converted successfully; otherwise, false. + public static bool TryParse(ReadOnlySpan utf8Text, out decimal result) => TryParse(utf8Text, NumberStyles.Number, provider: null, out result); public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out decimal result) { @@ -574,8 +557,7 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I result = 0; return false; } - - return Number.TryParseDecimal(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + return Number.TryParseDecimal(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out decimal result) @@ -1838,5 +1820,29 @@ private static bool TryConvertTo(decimal value, [MaybeNullWhen(false)] o /// public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, out decimal result) => TryParse(s, NumberStyles.Number, provider, out result); + + // + // IUtf8SpanParsable + // + + /// + public static decimal Parse(ReadOnlySpan utf8Text, NumberStyles style = NumberStyles.Number, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.ParseDecimal(utf8Text, style, NumberFormatInfo.GetInstance(provider)); + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out decimal result) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseDecimal(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + } + + /// + public static decimal Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) => Parse(utf8Text, NumberStyles.Number, provider); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out decimal result) => TryParse(utf8Text, NumberStyles.Number, provider, out result); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Double.cs b/src/libraries/System.Private.CoreLib/src/System/Double.cs index ef0d4dbc75d05..82ad0ca4b8dda 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Double.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Double.cs @@ -406,6 +406,12 @@ public static double Parse(ReadOnlySpan s, NumberStyles style = NumberStyl public static bool TryParse(ReadOnlySpan s, out double result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null, out result); + /// Tries to convert a UTF-8 character span containing the string representation of a number to its double-precision floating-point number equivalent. + /// A read-only UTF-8 character span that contains the number to convert. + /// When this method returns, contains a double-precision floating-point number equivalent of the numeric value or symbol contained in if the conversion succeeded or zero if the conversion failed. The conversion fails if the is or is not in a valid format. This parameter is passed uninitialized; any value originally supplied in result will be overwritten. + /// true if was converted successfully; otherwise, false. + public static bool TryParse(ReadOnlySpan utf8Text, out double result) => TryParse(utf8Text, NumberStyles.Float | NumberStyles.AllowThousands, provider: null, out result); + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out double result) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); @@ -2152,6 +2158,30 @@ public static double TanPi(double x) /// static double IUnaryPlusOperators.operator +(double value) => (double)(+value); + // + // IUtf8SpanParsable + // + + /// + public static double Parse(ReadOnlySpan utf8Text, NumberStyles style = NumberStyles.Float | NumberStyles.AllowThousands, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.ParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider)); + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out double result) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result); + } + + /// + public static double Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) => Parse(utf8Text, NumberStyles.Float | NumberStyles.AllowThousands, provider); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out double result) => TryParse(utf8Text, NumberStyles.Float | NumberStyles.AllowThousands, provider, out result); + // // IBinaryFloatParseAndFormatInfo // diff --git a/src/libraries/System.Private.CoreLib/src/System/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs index 48ac9366c06fa..ef5ee130fe284 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -349,6 +349,12 @@ public static Half Parse(ReadOnlySpan s, NumberStyles style = DefaultParse /// if the parse was successful, otherwise. public static bool TryParse(ReadOnlySpan s, out Half result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null, out result); + /// Tries to convert a UTF-8 character span containing the string representation of a number to its half-precision floating-point number equivalent. + /// A read-only UTF-8 character span that contains the number to convert. + /// When this method returns, contains a half-precision floating-point number equivalent of the numeric value or symbol contained in if the conversion succeeded or zero if the conversion failed. The conversion fails if the is or is not in a valid format. This parameter is passed uninitialized; any value originally supplied in result will be overwritten. + /// true if was converted successfully; otherwise, false. + public static bool TryParse(ReadOnlySpan utf8Text, out Half result) => TryParse(utf8Text, NumberStyles.Float | NumberStyles.AllowThousands, provider: null, out result); + /// /// Tries to parse a from a with the given and . /// @@ -1997,6 +2003,30 @@ public static (Half SinPi, Half CosPi) SinCosPi(Half x) /// public static Half operator +(Half value) => value; + // + // IUtf8SpanParsable + // + + /// + public static Half Parse(ReadOnlySpan utf8Text, NumberStyles style = NumberStyles.Float | NumberStyles.AllowThousands, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.ParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider)); + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out Half result) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result); + } + + /// + public static Half Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) => Parse(utf8Text, NumberStyles.Float | NumberStyles.AllowThousands, provider); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out Half result) => TryParse(utf8Text, NumberStyles.Float | NumberStyles.AllowThousands, provider, out result); + // // IBinaryFloatParseAndFormatInfo // diff --git a/src/libraries/System.Private.CoreLib/src/System/Int128.cs b/src/libraries/System.Private.CoreLib/src/System/Int128.cs index 4539d045d00e9..8af972e78fdbc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int128.cs @@ -148,6 +148,12 @@ public static Int128 Parse(ReadOnlySpan s, NumberStyles style = NumberStyl public static bool TryParse(ReadOnlySpan s, out Int128 result) => TryParse(s, NumberStyles.Integer, provider: null, out result); + /// Tries to convert a UTF-8 character span containing the string representation of a number to its 128-bit signed integer equivalent. + /// A span containing the UTF-8 characters representing the number to convert. + /// When this method returns, contains the 128-bit signed integer value equivalent to the number contained in if the conversion succeeded, or zero if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten. + /// true if was converted successfully; otherwise, false. + public static bool TryParse(ReadOnlySpan utf8Text, out Int128 result) => TryParse(utf8Text, NumberStyles.Integer, provider: null, out result); + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Int128 result) { NumberFormatInfo.ValidateParseStyleInteger(style); @@ -2170,6 +2176,30 @@ static bool INumberBase.TryConvertToTruncating(Int128 value, [Ma /// public static Int128 operator +(Int128 value) => value; + // + // IUtf8SpanParsable + // + + /// + public static Int128 Parse(ReadOnlySpan utf8Text, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.ParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider)); + } + + /// + 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; + } + + /// + public static Int128 Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) => Parse(utf8Text, NumberStyles.Integer, provider); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out Int128 result) => TryParse(utf8Text, NumberStyles.Integer, provider, out result); + // // IBinaryIntegerParseAndFormatInfo // diff --git a/src/libraries/System.Private.CoreLib/src/System/Int16.cs b/src/libraries/System.Private.CoreLib/src/System/Int16.cs index 286742abf8d30..7417bd5b9639d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int16.cs @@ -148,6 +148,12 @@ public static short Parse(ReadOnlySpan s, NumberStyles style = NumberStyle public static bool TryParse(ReadOnlySpan s, out short result) => TryParse(s, NumberStyles.Integer, provider: null, out result); + /// Tries to convert a UTF-8 character span containing the string representation of a number to its 16-bit signed integer equivalent. + /// A span containing the UTF-8 characters representing the number to convert. + /// When this method returns, contains the 16-bit signed integer value equivalent to the number contained in if the conversion succeeded, or zero if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten. + /// true if was converted successfully; otherwise, false. + public static bool TryParse(ReadOnlySpan utf8Text, out short result) => TryParse(utf8Text, NumberStyles.Integer, provider: null, out result); + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out short result) { NumberFormatInfo.ValidateParseStyleInteger(style); @@ -1365,6 +1371,30 @@ static bool INumberBase.TryConvertToTruncating(short value, [Mayb /// static short IUnaryPlusOperators.operator +(short value) => (short)(+value); + // + // IUtf8SpanParsable + // + + /// + public static short Parse(ReadOnlySpan utf8Text, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.ParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider)); + } + + /// + 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; + } + + /// + public static short Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) => Parse(utf8Text, NumberStyles.Integer, provider); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out short result) => TryParse(utf8Text, NumberStyles.Integer, provider, out result); + // // IBinaryIntegerParseAndFormatInfo // diff --git a/src/libraries/System.Private.CoreLib/src/System/Int32.cs b/src/libraries/System.Private.CoreLib/src/System/Int32.cs index 1097eb4b5dc73..dec399531fb53 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int32.cs @@ -158,6 +158,12 @@ public static int Parse(ReadOnlySpan s, NumberStyles style = NumberStyles. public static bool TryParse(ReadOnlySpan s, out int result) => TryParse(s, NumberStyles.Integer, provider: null, out result); + /// Tries to convert a UTF-8 character span containing the string representation of a number to its 32-bit signed integer equivalent. + /// A span containing the UTF-8 characters representing the number to convert. + /// When this method returns, contains the 32-bit signed integer value equivalent to the number contained in if the conversion succeeded, or zero if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten. + /// true if was converted successfully; otherwise, false. + public static bool TryParse(ReadOnlySpan utf8Text, out int result) => TryParse(utf8Text, NumberStyles.Integer, provider: null, out result); + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out int result) { NumberFormatInfo.ValidateParseStyleInteger(style); @@ -1401,6 +1407,30 @@ static bool INumberBase.TryConvertToTruncating(int value, [MaybeNul /// static int IUnaryPlusOperators.operator +(int value) => +value; + // + // IUtf8SpanParsable + // + + /// + public static int Parse(ReadOnlySpan utf8Text, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.ParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider)); + } + + /// + 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; + } + + /// + public static int Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) => Parse(utf8Text, NumberStyles.Integer, provider); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out int result) => TryParse(utf8Text, NumberStyles.Integer, provider, out result); + // // IBinaryIntegerParseAndFormatInfo // diff --git a/src/libraries/System.Private.CoreLib/src/System/Int64.cs b/src/libraries/System.Private.CoreLib/src/System/Int64.cs index 4db12f8b80ec3..a99f3ccd47ff9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int64.cs @@ -155,6 +155,12 @@ public static long Parse(ReadOnlySpan s, NumberStyles style = NumberStyles public static bool TryParse(ReadOnlySpan s, out long result) => TryParse(s, NumberStyles.Integer, provider: null, out result); + /// Tries to convert a UTF-8 character span containing the string representation of a number to its 64-bit signed integer equivalent. + /// A span containing the UTF-8 characters representing the number to convert. + /// When this method returns, contains the 64-bit signed integer value equivalent to the number contained in if the conversion succeeded, or zero if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten. + /// true if was converted successfully; otherwise, false. + public static bool TryParse(ReadOnlySpan utf8Text, out long result) => TryParse(utf8Text, NumberStyles.Integer, provider: null, out result); + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out long result) { NumberFormatInfo.ValidateParseStyleInteger(style); @@ -1404,6 +1410,30 @@ static bool INumberBase.TryConvertToTruncating(long value, [MaybeN /// static long IUnaryPlusOperators.operator +(long value) => +value; + // + // IUtf8SpanParsable + // + + /// + public static long Parse(ReadOnlySpan utf8Text, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.ParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider)); + } + + /// + 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; + } + + /// + public static long Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) => Parse(utf8Text, NumberStyles.Integer, provider); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out long result) => TryParse(utf8Text, NumberStyles.Integer, provider, out result); + // // IBinaryIntegerParseAndFormatInfo // diff --git a/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs b/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs index 0457b6917bba9..60c1b9f89f26b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IntPtr.cs @@ -247,6 +247,16 @@ public static bool TryParse(ReadOnlySpan s, out nint result) return nint_t.TryParse(s, out Unsafe.As(ref result)); } + /// Tries to convert a UTF-8 character span containing the string representation of a number to its signed integer equivalent. + /// A span containing the UTF-8 characters representing the number to convert. + /// When this method returns, contains the signed integer value equivalent to the number contained in if the conversion succeeded, or zero if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten. + /// true if was converted successfully; otherwise, false. + public static bool TryParse(ReadOnlySpan utf8Text, out nint result) + { + Unsafe.SkipInit(out result); + return nint_t.TryParse(utf8Text, out Unsafe.As(ref result)); + } + /// Tries to parse a string into a value. /// A read-only span of characters containing a number to convert. /// An object that provides culture-specific formatting information about . @@ -1387,5 +1397,29 @@ static bool INumberBase.TryConvertToTruncating(nint value, [MaybeN /// static nint IUnaryPlusOperators.operator +(nint value) => +value; + + // + // IUtf8SpanParsable + // + + /// + public static nint Parse(ReadOnlySpan utf8Text, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) => (nint)nint_t.Parse(utf8Text, style, provider); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out nint result) + { + Unsafe.SkipInit(out result); + return nint_t.TryParse(utf8Text, style, provider, out Unsafe.As(ref result)); + } + + /// + public static nint Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) => (nint)nint_t.Parse(utf8Text, provider); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out nint result) + { + Unsafe.SkipInit(out result); + return nint_t.TryParse(utf8Text, provider, out Unsafe.As(ref result)); + } } } 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 56da66e7066a4..d7ca1e866a51a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -964,7 +964,8 @@ private static ParsingStatus TryParseBinaryIntegerHexOrBinaryNumberStyle value, NumberStyles styles, NumberFormatInfo info) + internal static decimal ParseDecimal(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) + where TChar : unmanaged, IUtfChar { ParsingStatus status = TryParseDecimal(value, styles, info, out decimal result); if (status != ParsingStatus.OK) @@ -1110,7 +1111,8 @@ internal static TFloat ParseFloat(ReadOnlySpan value, Numb return result; } - internal static ParsingStatus TryParseDecimal(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out decimal result) + internal static ParsingStatus TryParseDecimal(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out decimal result) + where TChar : unmanaged, IUtfChar { NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, stackalloc byte[DecimalNumberBufferLength]); 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 7987c7eb89f15..410a902d3a6cc 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 @@ -700,7 +700,7 @@ public static bool TryParse([NotNullWhen(true)] string? s, out NFloat result) /// Tries to convert a character span containing the string representation of a number to its floating-point number equivalent. /// A read-only character span that contains the number to convert. - /// When this method returns, contains a floating-point number equivalent of the numeric value or symbol contained in if the conversion succeeded or zero if the conversion failed. The conversion fails if the is or is not in a valid format. This parameter is passed uninitialized; any value originally supplied in result will be overwritten. + /// When this method returns, contains a floating-point number equivalent of the numeric value or symbol contained in if the conversion succeeded or zero if the conversion failed. The conversion fails if the is or is not in a valid format. This parameter is passed uninitialized; any value originally supplied in result will be overwritten. /// true if was converted successfully; otherwise, false. public static bool TryParse(ReadOnlySpan s, out NFloat result) { @@ -708,6 +708,16 @@ public static bool TryParse(ReadOnlySpan s, out NFloat result) return NativeType.TryParse(s, out Unsafe.As(ref result)); } + /// Tries to convert a UTF-8 character span containing the string representation of a number to its floating-point number equivalent. + /// A read-only UTF-8 character span that contains the number to convert. + /// When this method returns, contains a floating-point number equivalent of the numeric value or symbol contained in if the conversion succeeded or zero if the conversion failed. The conversion fails if the is or is not in a valid format. This parameter is passed uninitialized; any value originally supplied in result will be overwritten. + /// true if was converted successfully; otherwise, false. + public static bool TryParse(ReadOnlySpan utf8Text, out NFloat result) + { + Unsafe.SkipInit(out result); + return NativeType.TryParse(utf8Text, out Unsafe.As(ref result)); + } + /// Tries to convert the string representation of a number in a specified style and culture-specific format to its floating-point number equivalent. /// A read-only character span that contains the number to convert. /// A bitwise combination of enumeration values that indicate the style elements that can be present in . @@ -1866,5 +1876,29 @@ public static (NFloat SinPi, NFloat CosPi) SinCosPi(NFloat x) /// public static NFloat TanPi(NFloat x) => new NFloat(NativeType.TanPi(x._value)); + + // + // IUtf8SpanParsable + // + + /// + public static NFloat Parse(ReadOnlySpan utf8Text, NumberStyles style = NumberStyles.Float | NumberStyles.AllowThousands, IFormatProvider? provider = null) + { + var result = NativeType.Parse(utf8Text, style, provider); + return new NFloat(result); + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out NFloat result) + { + Unsafe.SkipInit(out result); + return NativeType.TryParse(utf8Text, style, provider, out Unsafe.As(ref result)); + } + + /// + public static NFloat Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) => Parse(utf8Text, NumberStyles.Float | NumberStyles.AllowThousands, provider); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out NFloat result) => TryParse(utf8Text, NumberStyles.Float | NumberStyles.AllowThousands, provider, out result); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/SByte.cs b/src/libraries/System.Private.CoreLib/src/System/SByte.cs index b134a5d72ada8..fd1b9a48b36fb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SByte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SByte.cs @@ -151,6 +151,12 @@ public static sbyte Parse(ReadOnlySpan s, NumberStyles style = NumberStyle public static bool TryParse(ReadOnlySpan s, out sbyte result) => TryParse(s, NumberStyles.Integer, provider: null, out result); + /// Tries to convert a UTF-8 character span containing the string representation of a number to its 8-bit signed integer equivalent. + /// A span containing the UTF-8 characters representing the number to convert. + /// When this method returns, contains the 8-bit signed integer value equivalent to the number contained in if the conversion succeeded, or zero if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten. + /// true if was converted successfully; otherwise, false. + public static bool TryParse(ReadOnlySpan utf8Text, out sbyte result) => TryParse(utf8Text, NumberStyles.Integer, provider: null, out result); + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out sbyte result) { NumberFormatInfo.ValidateParseStyleInteger(style); @@ -1326,6 +1332,30 @@ static bool INumberBase.TryConvertToTruncating(sbyte value, [Mayb /// static sbyte IUnaryPlusOperators.operator +(sbyte value) => (sbyte)(+value); + // + // IUtf8SpanParsable + // + + /// + public static sbyte Parse(ReadOnlySpan utf8Text, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.ParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider)); + } + + /// + 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; + } + + /// + public static sbyte Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) => Parse(utf8Text, NumberStyles.Integer, provider); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out sbyte result) => TryParse(utf8Text, NumberStyles.Integer, provider, out result); + // // IBinaryIntegerParseAndFormatInfo // diff --git a/src/libraries/System.Private.CoreLib/src/System/Single.cs b/src/libraries/System.Private.CoreLib/src/System/Single.cs index a3789b906eb16..65b80f7d0d8e6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Single.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Single.cs @@ -401,6 +401,12 @@ public static float Parse(ReadOnlySpan s, NumberStyles style = NumberStyle public static bool TryParse(ReadOnlySpan s, out float result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null, out result); + /// Tries to convert a UTF-8 character span containing the string representation of a number to its single-precision floating-point number equivalent. + /// A read-only UTF-8 character span that contains the number to convert. + /// When this method returns, contains a single-precision floating-point number equivalent of the numeric value or symbol contained in if the conversion succeeded or zero if the conversion failed. The conversion fails if the is or is not in a valid format. This parameter is passed uninitialized; any value originally supplied in result will be overwritten. + /// true if was converted successfully; otherwise, false. + public static bool TryParse(ReadOnlySpan utf8Text, out float result) => TryParse(utf8Text, NumberStyles.Float | NumberStyles.AllowThousands, provider: null, out result); + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out float result) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); @@ -2032,6 +2038,30 @@ public static float TanPi(float x) /// static float IUnaryPlusOperators.operator +(float value) => (float)(+value); + // + // IUtf8SpanParsable + // + + /// + public static float Parse(ReadOnlySpan utf8Text, NumberStyles style = NumberStyles.Float | NumberStyles.AllowThousands, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.ParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider)); + } + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out float result) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.TryParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result); + } + + /// + public static float Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) => Parse(utf8Text, NumberStyles.Float | NumberStyles.AllowThousands, provider); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out float result) => TryParse(utf8Text, NumberStyles.Float | NumberStyles.AllowThousands, provider, out result); + // // IBinaryFloatParseAndFormatInfo // diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt128.cs b/src/libraries/System.Private.CoreLib/src/System/UInt128.cs index 90983735b11f0..37a57fd80abbe 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt128.cs @@ -149,6 +149,12 @@ public static UInt128 Parse(ReadOnlySpan s, NumberStyles style = NumberSty public static bool TryParse(ReadOnlySpan s, out UInt128 result) => TryParse(s, NumberStyles.Integer, provider: null, out result); + /// Tries to convert a UTF-8 character span containing the string representation of a number to its 128-bit unsigned integer equivalent. + /// A span containing the UTF-8 characters representing the number to convert. + /// When this method returns, contains the 128-bit unsigned integer value equivalent to the number contained in if the conversion succeeded, or zero if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten. + /// true if was converted successfully; otherwise, false. + public static bool TryParse(ReadOnlySpan utf8Text, out UInt128 result) => TryParse(utf8Text, NumberStyles.Integer, provider: null, out result); + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out UInt128 result) { NumberFormatInfo.ValidateParseStyleInteger(style); @@ -2133,6 +2139,30 @@ static bool INumberBase.TryConvertToTruncating(UInt128 value, [ /// public static UInt128 operator +(UInt128 value) => value; + // + // IUtf8SpanParsable + // + + /// + public static UInt128 Parse(ReadOnlySpan utf8Text, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.ParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider)); + } + + /// + 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; + } + + /// + public static UInt128 Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) => Parse(utf8Text, NumberStyles.Integer, provider); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out UInt128 result) => TryParse(utf8Text, NumberStyles.Integer, provider, out result); + // // IBinaryIntegerParseAndFormatInfo // diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt16.cs b/src/libraries/System.Private.CoreLib/src/System/UInt16.cs index f3addefb2e41b..4c38a6f92dc1e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt16.cs @@ -143,6 +143,12 @@ public static ushort Parse(ReadOnlySpan s, NumberStyles style = NumberStyl public static bool TryParse(ReadOnlySpan s, out ushort result) => TryParse(s, NumberStyles.Integer, provider: null, out result); + /// Tries to convert a UTF-8 character span containing the string representation of a number to its 16-bit unsigned integer equivalent. + /// A span containing the UTF-8 characters representing the number to convert. + /// When this method returns, contains the 16-bit unsigned integer value equivalent to the number contained in if the conversion succeeded, or zero if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten. + /// true if was converted successfully; otherwise, false. + public static bool TryParse(ReadOnlySpan utf8Text, out ushort result) => TryParse(utf8Text, NumberStyles.Integer, provider: null, out result); + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out ushort result) { NumberFormatInfo.ValidateParseStyleInteger(style); @@ -1180,6 +1186,30 @@ static bool INumberBase.TryConvertToTruncating(ushort value, [Ma /// static ushort IUnaryPlusOperators.operator +(ushort value) => (ushort)(+value); + // + // IUtf8SpanParsable + // + + /// + public static ushort Parse(ReadOnlySpan utf8Text, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.ParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider)); + } + + /// + 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; + } + + /// + public static ushort Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) => Parse(utf8Text, NumberStyles.Integer, provider); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out ushort result) => TryParse(utf8Text, NumberStyles.Integer, provider, out result); + // // IBinaryIntegerParseAndFormatInfo // diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt32.cs b/src/libraries/System.Private.CoreLib/src/System/UInt32.cs index bcd54ea45a94d..7364d4b166ff1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt32.cs @@ -153,6 +153,12 @@ public static uint Parse(ReadOnlySpan s, NumberStyles style = NumberStyles public static bool TryParse(ReadOnlySpan s, out uint result) => TryParse(s, NumberStyles.Integer, provider: null, out result); + /// Tries to convert a UTF-8 character span containing the string representation of a number to its 32-bit unsigned integer equivalent. + /// A span containing the UTF-8 characters representing the number to convert. + /// When this method returns, contains the 32-bit unsigned integer value equivalent to the number contained in if the conversion succeeded, or zero if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten. + /// true if was converted successfully; otherwise, false. + public static bool TryParse(ReadOnlySpan utf8Text, out uint result) => TryParse(utf8Text, NumberStyles.Integer, provider: null, out result); + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out uint result) { NumberFormatInfo.ValidateParseStyleInteger(style); @@ -1219,6 +1225,30 @@ static bool INumberBase.TryConvertToTruncating(uint value, [MaybeN /// static uint IUnaryPlusOperators.operator +(uint value) => +value; + // + // IUtf8SpanParsable + // + + /// + public static uint Parse(ReadOnlySpan utf8Text, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.ParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider)); + } + + /// + 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; + } + + /// + public static uint Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) => Parse(utf8Text, NumberStyles.Integer, provider); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out uint result) => TryParse(utf8Text, NumberStyles.Integer, provider, out result); + // // IBinaryIntegerParseAndFormatInfo // diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt64.cs b/src/libraries/System.Private.CoreLib/src/System/UInt64.cs index edb8becbc8e50..0342b4fda5dd3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt64.cs @@ -152,6 +152,12 @@ public static ulong Parse(ReadOnlySpan s, NumberStyles style = NumberStyle public static bool TryParse(ReadOnlySpan s, out ulong result) => TryParse(s, NumberStyles.Integer, provider: null, out result); + /// Tries to convert a UTF-8 character span containing the string representation of a number to its 64-bit unsigned integer equivalent. + /// A span containing the UTF-8 characters representing the number to convert. + /// When this method returns, contains the 64-bit unsigned integer value equivalent to the number contained in if the conversion succeeded, or zero if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten. + /// true if was converted successfully; otherwise, false. + public static bool TryParse(ReadOnlySpan utf8Text, out ulong result) => TryParse(utf8Text, NumberStyles.Integer, provider: null, out result); + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out ulong result) { NumberFormatInfo.ValidateParseStyleInteger(style); @@ -1212,6 +1218,30 @@ static bool INumberBase.TryConvertToTruncating(ulong value, [Mayb /// static ulong IUnaryPlusOperators.operator +(ulong value) => +value; + // + // IUtf8SpanParsable + // + + /// + public static ulong Parse(ReadOnlySpan utf8Text, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleInteger(style); + return Number.ParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider)); + } + + /// + 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; + } + + /// + public static ulong Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) => Parse(utf8Text, NumberStyles.Integer, provider); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out ulong result) => TryParse(utf8Text, NumberStyles.Integer, provider, out result); + // // IBinaryIntegerParseAndFormatInfo // diff --git a/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs b/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs index 6c2d77d29779a..440ad84f0675e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UIntPtr.cs @@ -247,6 +247,16 @@ public static bool TryParse(ReadOnlySpan s, out nuint result) return nuint_t.TryParse(s, out Unsafe.As(ref result)); } + /// Tries to convert a UTF-8 character span containing the string representation of a number to its unsigned integer equivalent. + /// A span containing the UTF-8 characters representing the number to convert. + /// When this method returns, contains the unsigned integer value equivalent to the number contained in if the conversion succeeded, or zero if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten. + /// true if was converted successfully; otherwise, false. + public static bool TryParse(ReadOnlySpan utf8Text, out nuint result) + { + Unsafe.SkipInit(out result); + return nuint_t.TryParse(utf8Text, out Unsafe.As(ref result)); + } + /// public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out nuint result) { @@ -1209,5 +1219,29 @@ static bool INumberBase.TryConvertToTruncating(nuint value, [Mayb /// static nuint IUnaryPlusOperators.operator +(nuint value) => +value; + + // + // IUtf8SpanParsable + // + + /// + public static nuint Parse(ReadOnlySpan utf8Text, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) => (nuint)nuint_t.Parse(utf8Text, style, provider); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out nuint result) + { + Unsafe.SkipInit(out result); + return nuint_t.TryParse(utf8Text, style, provider, out Unsafe.As(ref result)); + } + + /// + public static nuint Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) => (nuint)nuint_t.Parse(utf8Text, provider); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out nuint result) + { + Unsafe.SkipInit(out result); + return nuint_t.TryParse(utf8Text, provider, out Unsafe.As(ref result)); + } } } 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 87d43107a2c20..738b0dec7df0d 100644 --- a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs +++ b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs @@ -1444,6 +1444,8 @@ public static void Free(void* ptr) { } public static System.Runtime.InteropServices.NFloat operator -(System.Runtime.InteropServices.NFloat left, System.Runtime.InteropServices.NFloat right) { throw null; } public static System.Runtime.InteropServices.NFloat operator -(System.Runtime.InteropServices.NFloat value) { throw null; } public static System.Runtime.InteropServices.NFloat operator +(System.Runtime.InteropServices.NFloat value) { throw null; } + public static System.Runtime.InteropServices.NFloat Parse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.AllowDecimalPoint | System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite | System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.AllowTrailingWhite, System.IFormatProvider? provider = null) { throw null; } + public static System.Runtime.InteropServices.NFloat Parse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider) { throw null; } public static System.Runtime.InteropServices.NFloat Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.AllowDecimalPoint | System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite | System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.AllowTrailingWhite, System.IFormatProvider? provider = null) { throw null; } public static System.Runtime.InteropServices.NFloat Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { throw null; } public static System.Runtime.InteropServices.NFloat Parse(string s) { throw null; } @@ -1505,6 +1507,9 @@ public static void Free(void* ptr) { } public static System.Runtime.InteropServices.NFloat Truncate(System.Runtime.InteropServices.NFloat x) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Runtime.InteropServices.NFloat result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, out System.Runtime.InteropServices.NFloat result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, out System.Runtime.InteropServices.NFloat result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Runtime.InteropServices.NFloat result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out System.Runtime.InteropServices.NFloat result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out System.Runtime.InteropServices.NFloat result) { throw null; } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 6ba6eb6b06fb2..7accad6551a49 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -786,6 +786,8 @@ public static void SetByte(System.Array array, int index, byte value) { } public static byte Log2(byte value) { throw null; } public static byte Max(byte x, byte y) { throw null; } public static byte Min(byte x, byte y) { throw null; } + public static byte Parse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } + public static byte Parse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider) { throw null; } public static byte Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } public static byte Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { throw null; } public static byte Parse(string s) { throw null; } @@ -881,6 +883,9 @@ public static void SetByte(System.Array array, int index, byte value) { } public static byte TrailingZeroCount(byte value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, out byte result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out byte result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, out byte result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out byte result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out byte result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out byte result) { throw null; } @@ -2042,6 +2047,8 @@ public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, S public static decimal operator -(decimal d1, decimal d2) { throw null; } public static decimal operator -(decimal d) { throw null; } public static decimal operator +(decimal d) { throw null; } + public static decimal Parse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Number, System.IFormatProvider? provider = null) { throw null; } + public static decimal Parse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider) { throw null; } public static decimal Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Number, System.IFormatProvider? provider = null) { throw null; } public static decimal Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { throw null; } public static decimal Parse(string s) { throw null; } @@ -2124,6 +2131,9 @@ void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Ser public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public static bool TryGetBits(decimal d, System.Span destination, out int valuesWritten) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, out decimal result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out decimal result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, out decimal result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out decimal result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out decimal result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out decimal result) { throw null; } @@ -2288,6 +2298,8 @@ public DivideByZeroException(string? message, System.Exception? innerException) public static bool operator !=(double left, double right) { throw null; } public static bool operator <(double left, double right) { throw null; } public static bool operator <=(double left, double right) { throw null; } + public static double Parse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.AllowDecimalPoint | System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite | System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.AllowTrailingWhite, System.IFormatProvider? provider = null) { throw null; } + public static double Parse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider) { throw null; } public static double Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.AllowDecimalPoint | System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite | System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.AllowTrailingWhite, System.IFormatProvider? provider = null) { throw null; } public static double Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { throw null; } public static double Parse(string s) { throw null; } @@ -2366,6 +2378,9 @@ public DivideByZeroException(string? message, System.Exception? innerException) public static double Truncate(double x) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, out double result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out double result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, out double result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out double result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out double result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out double result) { throw null; } @@ -2971,6 +2986,8 @@ public enum GCNotificationStatus public static System.Half operator -(System.Half left, System.Half right) { throw null; } public static System.Half operator -(System.Half value) { throw null; } public static System.Half operator +(System.Half value) { throw null; } + public static System.Half Parse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.AllowDecimalPoint | System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite | System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.AllowTrailingWhite, System.IFormatProvider? provider = null) { throw null; } + public static System.Half Parse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider) { throw null; } public static System.Half Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.AllowDecimalPoint | System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite | System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.AllowTrailingWhite, System.IFormatProvider? provider = null) { throw null; } public static System.Half Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { throw null; } public static System.Half Parse(string s) { throw null; } @@ -3025,6 +3042,9 @@ public enum GCNotificationStatus public static System.Half Truncate(System.Half x) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Half result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, out System.Half result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, out System.Half result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Half result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out System.Half result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out System.Half result) { throw null; } @@ -3280,6 +3300,8 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep public static System.Int128 operator -(System.Int128 value) { throw null; } public static System.Int128 operator +(System.Int128 value) { throw null; } public static System.Int128 operator >>>(System.Int128 value, int shiftAmount) { throw null; } + public static System.Int128 Parse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } + public static System.Int128 Parse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider) { throw null; } public static System.Int128 Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } public static System.Int128 Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { throw null; } public static System.Int128 Parse(string s) { throw null; } @@ -3326,6 +3348,9 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep public static System.Int128 TrailingZeroCount(System.Int128 value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Int128 result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, out System.Int128 result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, out System.Int128 result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Int128 result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out System.Int128 result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out System.Int128 result) { throw null; } @@ -3371,6 +3396,8 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep public static short MaxMagnitude(short x, short y) { throw null; } public static short Min(short x, short y) { throw null; } public static short MinMagnitude(short x, short y) { throw null; } + public static short Parse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } + public static short Parse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider) { throw null; } public static short Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } public static short Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { throw null; } public static short Parse(string s) { throw null; } @@ -3460,6 +3487,9 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep public static short TrailingZeroCount(short value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out short result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, out short result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, out short result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out short result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out short result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out short result) { throw null; } @@ -3505,6 +3535,8 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep public static int MaxMagnitude(int x, int y) { throw null; } public static int Min(int x, int y) { throw null; } public static int MinMagnitude(int x, int y) { throw null; } + public static int Parse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } + public static int Parse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider) { throw null; } public static int Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } public static int Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { throw null; } public static int Parse(string s) { throw null; } @@ -3594,6 +3626,9 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep public static int TrailingZeroCount(int value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out int result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, out int result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, out int result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out int result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out int result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out int result) { throw null; } @@ -3639,6 +3674,8 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep public static long MaxMagnitude(long x, long y) { throw null; } public static long Min(long x, long y) { throw null; } public static long MinMagnitude(long x, long y) { throw null; } + public static long Parse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } + public static long Parse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider) { throw null; } public static long Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } public static long Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { throw null; } public static long Parse(string s) { throw null; } @@ -3728,6 +3765,9 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep public static long TrailingZeroCount(long value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out long result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, out long result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, out long result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out long result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out long result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out long result) { throw null; } @@ -3791,6 +3831,8 @@ public InsufficientMemoryException(string? message, System.Exception? innerExcep public unsafe static explicit operator nint (void* value) { throw null; } public static bool operator !=(nint value1, nint value2) { throw null; } public static nint operator -(nint pointer, int offset) { throw null; } + public static nint Parse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } + public static nint Parse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider) { throw null; } public static nint Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } public static nint Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { throw null; } public static nint Parse(string s) { throw null; } @@ -3869,6 +3911,9 @@ void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Ser public static nint TrailingZeroCount(nint value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out nint result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, out nint result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, out nint result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out nint result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out nint result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out nint result) { throw null; } @@ -4773,6 +4818,8 @@ public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, S public static sbyte MaxMagnitude(sbyte x, sbyte y) { throw null; } public static sbyte Min(sbyte x, sbyte y) { throw null; } public static sbyte MinMagnitude(sbyte x, sbyte y) { throw null; } + public static sbyte Parse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } + public static sbyte Parse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider) { throw null; } public static sbyte Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } public static sbyte Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { throw null; } public static sbyte Parse(string s) { throw null; } @@ -4862,6 +4909,9 @@ public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, S public static sbyte TrailingZeroCount(sbyte value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out sbyte result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, out sbyte result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, out sbyte result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out sbyte result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out sbyte result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out sbyte result) { throw null; } @@ -4982,6 +5032,8 @@ public SerializableAttribute() { } public static bool operator !=(float left, float right) { throw null; } public static bool operator <(float left, float right) { throw null; } public static bool operator <=(float left, float right) { throw null; } + public static float Parse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.AllowDecimalPoint | System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite | System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.AllowTrailingWhite, System.IFormatProvider? provider = null) { throw null; } + public static float Parse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider) { throw null; } public static float Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.AllowDecimalPoint | System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite | System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.AllowTrailingWhite, System.IFormatProvider? provider = null) { throw null; } public static float Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { throw null; } public static float Parse(string s) { throw null; } @@ -5060,6 +5112,9 @@ public SerializableAttribute() { } public static float Truncate(float x) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out float result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, out float result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, out float result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out float result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out float result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out float result) { throw null; } @@ -6407,6 +6462,8 @@ public TypeUnloadedException(string? message, System.Exception? innerException) public static System.UInt128 operator -(System.UInt128 value) { throw null; } public static System.UInt128 operator +(System.UInt128 value) { throw null; } public static System.UInt128 operator >>>(System.UInt128 value, int shiftAmount) { throw null; } + public static System.UInt128 Parse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } + public static System.UInt128 Parse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider) { throw null; } public static System.UInt128 Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } public static System.UInt128 Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { throw null; } public static System.UInt128 Parse(string s) { throw null; } @@ -6459,6 +6516,9 @@ public TypeUnloadedException(string? message, System.Exception? innerException) public static System.UInt128 TrailingZeroCount(System.UInt128 value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.UInt128 result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, out System.UInt128 result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, out System.UInt128 result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.UInt128 result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out System.UInt128 result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out System.UInt128 result) { throw null; } @@ -6498,6 +6558,8 @@ public TypeUnloadedException(string? message, System.Exception? innerException) public static ushort Log2(ushort value) { throw null; } public static ushort Max(ushort x, ushort y) { throw null; } public static ushort Min(ushort x, ushort y) { throw null; } + public static ushort Parse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } + public static ushort Parse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider) { throw null; } public static ushort Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } public static ushort Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { throw null; } public static ushort Parse(string s) { throw null; } @@ -6593,6 +6655,9 @@ public TypeUnloadedException(string? message, System.Exception? innerException) public static ushort TrailingZeroCount(ushort value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out ushort result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, out ushort result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, out ushort result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out ushort result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out ushort result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out ushort result) { throw null; } @@ -6632,6 +6697,8 @@ public TypeUnloadedException(string? message, System.Exception? innerException) public static uint Log2(uint value) { throw null; } public static uint Max(uint x, uint y) { throw null; } public static uint Min(uint x, uint y) { throw null; } + public static uint Parse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } + public static uint Parse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider) { throw null; } public static uint Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } public static uint Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { throw null; } public static uint Parse(string s) { throw null; } @@ -6727,6 +6794,9 @@ public TypeUnloadedException(string? message, System.Exception? innerException) public static uint TrailingZeroCount(uint value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out uint result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, out uint result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, out uint result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out uint result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out uint result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out uint result) { throw null; } @@ -6766,6 +6836,8 @@ public TypeUnloadedException(string? message, System.Exception? innerException) public static ulong Log2(ulong value) { throw null; } public static ulong Max(ulong x, ulong y) { throw null; } public static ulong Min(ulong x, ulong y) { throw null; } + public static ulong Parse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } + public static ulong Parse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider) { throw null; } public static ulong Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } public static ulong Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { throw null; } public static ulong Parse(string s) { throw null; } @@ -6861,6 +6933,9 @@ public TypeUnloadedException(string? message, System.Exception? innerException) public static ulong TrailingZeroCount(ulong value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out ulong result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, out ulong result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, out ulong result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out ulong result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out ulong result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out ulong result) { throw null; } @@ -6915,6 +6990,8 @@ public TypeUnloadedException(string? message, System.Exception? innerException) public unsafe static explicit operator nuint (void* value) { throw null; } public static bool operator !=(nuint value1, nuint value2) { throw null; } public static nuint operator -(nuint pointer, int offset) { throw null; } + public static nuint Parse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } + public static nuint Parse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider) { throw null; } public static nuint Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, System.IFormatProvider? provider = null) { throw null; } public static nuint Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { throw null; } public static nuint Parse(string s) { throw null; } @@ -6998,6 +7075,9 @@ void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Ser public static nuint TrailingZeroCount(nuint value) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out nuint result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, out nuint result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, out nuint result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out nuint result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out nuint result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out nuint result) { throw null; } From 01066c8fe46a11bfe84f6b25a111cab65d969d7f Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 29 May 2023 11:42:40 -0700 Subject: [PATCH 06/19] Adding tests covering the new UTF-8 parsing support --- .../System.Runtime/tests/System/ByteTests.cs | 43 +++++++++++++++++ .../tests/System/DecimalTests.cs | 44 ++++++++++++++++++ .../tests/System/DoubleTests.cs | 44 ++++++++++++++++++ .../System.Runtime/tests/System/HalfTests.cs | 46 +++++++++++++++++++ .../tests/System/Int128Tests.cs | 44 ++++++++++++++++++ .../System.Runtime/tests/System/Int16Tests.cs | 44 ++++++++++++++++++ .../System.Runtime/tests/System/Int32Tests.cs | 44 ++++++++++++++++++ .../System.Runtime/tests/System/Int64Tests.cs | 44 ++++++++++++++++++ .../tests/System/IntPtrTests.cs | 44 ++++++++++++++++++ .../System.Runtime/tests/System/SByteTests.cs | 43 +++++++++++++++++ .../tests/System/SingleTests.cs | 44 ++++++++++++++++++ .../tests/System/UInt128Tests.cs | 44 ++++++++++++++++++ .../tests/System/UInt16Tests.cs | 43 +++++++++++++++++ .../tests/System/UInt32Tests.cs | 44 ++++++++++++++++++ .../tests/System/UInt64Tests.cs | 44 ++++++++++++++++++ .../tests/System/UIntPtrTests.cs | 44 ++++++++++++++++++ 16 files changed, 703 insertions(+) diff --git a/src/libraries/System.Runtime/tests/System/ByteTests.cs b/src/libraries/System.Runtime/tests/System/ByteTests.cs index b0b309e47892d..ac05451bedb70 100644 --- a/src/libraries/System.Runtime/tests/System/ByteTests.cs +++ b/src/libraries/System.Runtime/tests/System/ByteTests.cs @@ -369,6 +369,49 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP } } + [Theory] + [MemberData(nameof(Parse_ValidWithOffsetCount_TestData))] + public static void Parse_Utf8Span_Valid(string value, int offset, int count, NumberStyles style, IFormatProvider provider, byte expected) + { + byte result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value, offset, count); + + // Default style and provider + if (style == NumberStyles.Integer && provider == null) + { + Assert.True(byte.TryParse(valueUtf8, out result)); + Assert.Equal(expected, result); + } + + Assert.Equal(expected, byte.Parse(valueUtf8, style, provider)); + + Assert.True(byte.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value != null) + { + byte result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value); + + // Default style and provider + if (style == NumberStyles.Integer && provider == null) + { + Assert.False(byte.TryParse(valueUtf8, out result)); + Assert.Equal(0u, result); + } + + Assert.Throws(exceptionType, () => byte.Parse(valueUtf8, style, provider)); + + Assert.False(byte.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(0u, result); + } + } + [Theory] [MemberData(nameof(ToString_TestData))] public static void TryFormat(byte i, string format, IFormatProvider provider, string expected) => diff --git a/src/libraries/System.Runtime/tests/System/DecimalTests.cs b/src/libraries/System.Runtime/tests/System/DecimalTests.cs index e4ef2ace97a34..913f3ba13be87 100644 --- a/src/libraries/System.Runtime/tests/System/DecimalTests.cs +++ b/src/libraries/System.Runtime/tests/System/DecimalTests.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Linq; using System.Numerics; +using System.Text; using Microsoft.DotNet.RemoteExecutor; using Xunit; @@ -1000,6 +1001,49 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP } } + [Theory] + [MemberData(nameof(Parse_ValidWithOffsetCount_TestData))] + public static void Parse_Utf8Span_Valid(string value, int offset, int count, NumberStyles style, IFormatProvider provider, decimal expected) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + + decimal result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value, offset, count); + + if ((style & ~NumberStyles.Number) == 0 && style != NumberStyles.None) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.True(decimal.TryParse(valueUtf8, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, decimal.Parse(valueUtf8)); + } + + Assert.Equal(expected, decimal.Parse(valueUtf8, provider: provider)); + } + + Assert.Equal(expected, decimal.Parse(valueUtf8, style, provider)); + + Assert.True(decimal.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value != null) + { + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value); + Assert.Throws(exceptionType, () => decimal.Parse(valueUtf8, style, provider)); + + Assert.False(decimal.TryParse(valueUtf8, style, provider, out decimal result)); + Assert.Equal(0, result); + } + } + public static IEnumerable Remainder_Valid_TestData() { decimal NegativeZero = new decimal(0, 0, 0, true, 0); diff --git a/src/libraries/System.Runtime/tests/System/DoubleTests.cs b/src/libraries/System.Runtime/tests/System/DoubleTests.cs index b2a93df11b480..0f51597314d78 100644 --- a/src/libraries/System.Runtime/tests/System/DoubleTests.cs +++ b/src/libraries/System.Runtime/tests/System/DoubleTests.cs @@ -6,6 +6,7 @@ using System.IO; using System.IO.Tests; using System.Linq; +using System.Text; using Xunit; #pragma warning disable xUnit1025 // reporting duplicate test cases due to not distinguishing 0.0 from -0.0, NaN from -NaN @@ -615,6 +616,49 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP } } + [Theory] + [MemberData(nameof(Parse_ValidWithOffsetCount_TestData))] + public static void Parse_Utf8Span_Valid(string value, int offset, int count, NumberStyles style, IFormatProvider provider, double expected) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + + double result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value, offset, count); + + if ((style & ~(NumberStyles.Float | NumberStyles.AllowThousands)) == 0 && style != NumberStyles.None) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.True(double.TryParse(valueUtf8, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, double.Parse(valueUtf8)); + } + + Assert.Equal(expected, double.Parse(valueUtf8, provider: provider)); + } + + Assert.Equal(expected, double.Parse(valueUtf8, style, provider)); + + Assert.True(double.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value != null) + { + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value); + Assert.Throws(exceptionType, () => double.Parse(valueUtf8, style, provider)); + + Assert.False(double.TryParse(valueUtf8, style, provider, out double result)); + Assert.Equal(0, result); + } + } + [Fact] public static void PositiveInfinity() { diff --git a/src/libraries/System.Runtime/tests/System/HalfTests.cs b/src/libraries/System.Runtime/tests/System/HalfTests.cs index cae21d055f4d4..7c77877de4cd7 100644 --- a/src/libraries/System.Runtime/tests/System/HalfTests.cs +++ b/src/libraries/System.Runtime/tests/System/HalfTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Dynamic; using System.Globalization; +using System.Text; using Xunit; namespace System.Tests @@ -882,6 +883,51 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP } } + [Theory] + [MemberData(nameof(Parse_ValidWithOffsetCount_TestData))] + public static void Parse_Utf8Span_Valid(string value, int offset, int count, NumberStyles style, IFormatProvider provider, float expectedFloat) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + + Half result; + Half expected = (Half)expectedFloat; + + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value, offset, count); + + if ((style & ~(NumberStyles.Float | NumberStyles.AllowThousands)) == 0 && style != NumberStyles.None) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.True(Half.TryParse(valueUtf8, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Half.Parse(valueUtf8)); + } + + Assert.Equal(expected, Half.Parse(valueUtf8, provider: provider)); + } + + Assert.True(expected.Equals(Half.Parse(valueUtf8, style, provider)) || (Half.IsNaN(expected) && Half.IsNaN(Half.Parse(value.AsSpan(offset, count), style, provider)))); + + Assert.True(Half.TryParse(valueUtf8, style, provider, out result)); + Assert.True(expected.Equals(result) || (Half.IsNaN(expected) && Half.IsNaN(result))); + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value != null) + { + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value); + Assert.Throws(exceptionType, () => float.Parse(valueUtf8, style, provider)); + + Assert.False(float.TryParse(valueUtf8, style, provider, out float result)); + Assert.Equal(0, result); + } + } + public static IEnumerable ToString_TestData() { yield return new object[] { -4570.0f, "G", null, "-4570" }; diff --git a/src/libraries/System.Runtime/tests/System/Int128Tests.cs b/src/libraries/System.Runtime/tests/System/Int128Tests.cs index b935973db9017..3eb302ac01b1d 100644 --- a/src/libraries/System.Runtime/tests/System/Int128Tests.cs +++ b/src/libraries/System.Runtime/tests/System/Int128Tests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; using System.Numerics; +using System.Text; using Xunit; namespace System.Tests @@ -439,6 +440,49 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP } } + [Theory] + [MemberData(nameof(Parse_ValidWithOffsetCount_TestData))] + public static void Parse_Utf8Span_Valid(string value, int offset, int count, NumberStyles style, IFormatProvider provider, Int128 expected) + { + Int128 result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value, offset, count); + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.True(Int128.TryParse(valueUtf8, out result)); + Assert.Equal(expected, result); + } + + Assert.Equal(expected, Int128.Parse(valueUtf8, style, provider)); + + Assert.True(Int128.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value is not null) + { + Int128 result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value); + + // Default style and provider + if ((style == NumberStyles.Integer) && (provider is null)) + { + Assert.False(Int128.TryParse(valueUtf8, out result)); + Assert.Equal(0, result); + } + + Assert.Throws(exceptionType, () => Int128.Parse(valueUtf8, style, provider)); + + Assert.False(Int128.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(0, result); + } + } + [Theory] [MemberData(nameof(ToString_TestData))] public static void TryFormat(Int128 i, string format, IFormatProvider provider, string expected) => diff --git a/src/libraries/System.Runtime/tests/System/Int16Tests.cs b/src/libraries/System.Runtime/tests/System/Int16Tests.cs index 13888e1880eb9..4b8e8ad460b3f 100644 --- a/src/libraries/System.Runtime/tests/System/Int16Tests.cs +++ b/src/libraries/System.Runtime/tests/System/Int16Tests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; +using System.Text; using Xunit; namespace System.Tests @@ -393,6 +394,49 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP } } + [Theory] + [MemberData(nameof(Parse_ValidWithOffsetCount_TestData))] + public static void Parse_Utf8Span_Valid(string value, int offset, int count, NumberStyles style, IFormatProvider provider, short expected) + { + short result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value, offset, count); + + // Default style and provider + if (style == NumberStyles.Integer && provider == null) + { + Assert.True(short.TryParse(valueUtf8, out result)); + Assert.Equal(expected, result); + } + + Assert.Equal(expected, short.Parse(valueUtf8, style, provider)); + + Assert.True(short.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value != null) + { + short result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value); + + // Default style and provider + if (style == NumberStyles.Integer && provider == null) + { + Assert.False(short.TryParse(valueUtf8, out result)); + Assert.Equal(0, result); + } + + Assert.Throws(exceptionType, () => short.Parse(valueUtf8, style, provider)); + + Assert.False(short.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(0, result); + } + } + [Theory] [MemberData(nameof(ToString_TestData))] public static void TryFormat(short i, string format, IFormatProvider provider, string expected) => diff --git a/src/libraries/System.Runtime/tests/System/Int32Tests.cs b/src/libraries/System.Runtime/tests/System/Int32Tests.cs index ae3be1be3288c..836dbdf4718b1 100644 --- a/src/libraries/System.Runtime/tests/System/Int32Tests.cs +++ b/src/libraries/System.Runtime/tests/System/Int32Tests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; +using System.Text; using Xunit; namespace System.Tests @@ -827,6 +828,49 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP } } + [Theory] + [MemberData(nameof(Parse_ValidWithOffsetCount_TestData))] + public static void Parse_Utf8Span_Valid(string value, int offset, int count, NumberStyles style, IFormatProvider provider, int expected) + { + int result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value, offset, count); + + // Default style and provider + if (style == NumberStyles.Integer && provider == null) + { + Assert.True(int.TryParse(valueUtf8, out result)); + Assert.Equal(expected, result); + } + + Assert.Equal(expected, int.Parse(valueUtf8, style, provider)); + + Assert.True(int.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value != null) + { + int result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value); + + // Default style and provider + if (style == NumberStyles.Integer && provider == null) + { + Assert.False(int.TryParse(valueUtf8, out result)); + Assert.Equal(0, result); + } + + Assert.Throws(exceptionType, () => int.Parse(valueUtf8, style, provider)); + + Assert.False(int.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(0, result); + } + } + [Theory] [InlineData("N")] [InlineData("F")] diff --git a/src/libraries/System.Runtime/tests/System/Int64Tests.cs b/src/libraries/System.Runtime/tests/System/Int64Tests.cs index b3ba3512d7041..fa1e69bf8d08a 100644 --- a/src/libraries/System.Runtime/tests/System/Int64Tests.cs +++ b/src/libraries/System.Runtime/tests/System/Int64Tests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; using System.Numerics; +using System.Text; using Xunit; namespace System.Tests @@ -421,6 +422,49 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP } } + [Theory] + [MemberData(nameof(Parse_ValidWithOffsetCount_TestData))] + public static void Parse_Utf8Span_Valid(string value, int offset, int count, NumberStyles style, IFormatProvider provider, long expected) + { + long result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value, offset, count); + + // Default style and provider + if (style == NumberStyles.Integer && provider == null) + { + Assert.True(long.TryParse(valueUtf8, out result)); + Assert.Equal(expected, result); + } + + Assert.Equal(expected, long.Parse(valueUtf8, style, provider)); + + Assert.True(long.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value != null) + { + long result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value); + + // Default style and provider + if (style == NumberStyles.Integer && provider == null) + { + Assert.False(long.TryParse(valueUtf8, out result)); + Assert.Equal(0, result); + } + + Assert.Throws(exceptionType, () => long.Parse(valueUtf8, style, provider)); + + Assert.False(long.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(0, result); + } + } + [Theory] [MemberData(nameof(ToString_TestData))] public static void TryFormat(long i, string format, IFormatProvider provider, string expected) => diff --git a/src/libraries/System.Runtime/tests/System/IntPtrTests.cs b/src/libraries/System.Runtime/tests/System/IntPtrTests.cs index 68f3b7f490329..48cf51d8dfc14 100644 --- a/src/libraries/System.Runtime/tests/System/IntPtrTests.cs +++ b/src/libraries/System.Runtime/tests/System/IntPtrTests.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Linq; using System.Reflection; +using System.Text; using Xunit; namespace System.Tests @@ -984,6 +985,49 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP } } + [Theory] + [MemberData(nameof(Parse_ValidWithOffsetCount_TestData))] + public static void Parse_Utf8Span_Valid(string value, int offset, int count, NumberStyles style, IFormatProvider provider, nint expected) + { + nint result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value, offset, count); + + // Default style and provider + if (style == NumberStyles.Integer && provider == null) + { + Assert.True(nint.TryParse(valueUtf8, out result)); + Assert.Equal(expected, result); + } + + Assert.Equal(expected, nint.Parse(valueUtf8, style, provider)); + + Assert.True(nint.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value != null) + { + nint result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value); + + // Default style and provider + if (style == NumberStyles.Integer && provider == null) + { + Assert.False(nint.TryParse(valueUtf8, out result)); + Assert.Equal(default, result); + } + + Assert.Throws(exceptionType, () => int.Parse(valueUtf8, style, provider)); + + Assert.False(nint.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(default, result); + } + } + [Theory] [InlineData("N")] [InlineData("F")] diff --git a/src/libraries/System.Runtime/tests/System/SByteTests.cs b/src/libraries/System.Runtime/tests/System/SByteTests.cs index a46fa67e95d4a..328ea50d71cea 100644 --- a/src/libraries/System.Runtime/tests/System/SByteTests.cs +++ b/src/libraries/System.Runtime/tests/System/SByteTests.cs @@ -388,6 +388,49 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP } } + [Theory] + [MemberData(nameof(Parse_ValidWithOffsetCount_TestData))] + public static void Parse_Utf8Span_Valid(string value, int offset, int count, NumberStyles style, IFormatProvider provider, sbyte expected) + { + sbyte result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value, offset, count); + + // Default style and provider + if (style == NumberStyles.Integer && provider == null) + { + Assert.True(sbyte.TryParse(valueUtf8, out result)); + Assert.Equal(expected, result); + } + + Assert.Equal(expected, sbyte.Parse(valueUtf8, style, provider)); + + Assert.True(sbyte.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value != null) + { + sbyte result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value); + + // Default style and provider + if (style == NumberStyles.Integer && provider == null) + { + Assert.False(sbyte.TryParse(valueUtf8, out result)); + Assert.Equal(0, result); + } + + Assert.Throws(exceptionType, () => sbyte.Parse(valueUtf8, style, provider)); + + Assert.False(sbyte.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(0, result); + } + } + [Theory] [MemberData(nameof(ToString_TestData))] public static void TryFormat(sbyte i, string format, IFormatProvider provider, string expected) => diff --git a/src/libraries/System.Runtime/tests/System/SingleTests.cs b/src/libraries/System.Runtime/tests/System/SingleTests.cs index 07414d8759668..122a142314d0b 100644 --- a/src/libraries/System.Runtime/tests/System/SingleTests.cs +++ b/src/libraries/System.Runtime/tests/System/SingleTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; +using System.Text; using Xunit; #pragma warning disable xUnit1025 // reporting duplicate test cases due to not distinguishing 0.0 from -0.0, NaN from -NaN @@ -554,6 +555,49 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP } } + [Theory] + [MemberData(nameof(Parse_ValidWithOffsetCount_TestData))] + public static void Parse_Utf8Span_Valid(string value, int offset, int count, NumberStyles style, IFormatProvider provider, float expected) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + + float result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value, offset, count); + + if ((style & ~(NumberStyles.Float | NumberStyles.AllowThousands)) == 0 && style != NumberStyles.None) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.True(float.TryParse(valueUtf8, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, float.Parse(valueUtf8)); + } + + Assert.Equal(expected, float.Parse(valueUtf8, provider: provider)); + } + + Assert.Equal(expected, float.Parse(valueUtf8, style, provider)); + + Assert.True(float.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value != null) + { + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value); + Assert.Throws(exceptionType, () => float.Parse(valueUtf8, style, provider)); + + Assert.False(float.TryParse(valueUtf8, style, provider, out float result)); + Assert.Equal(0, result); + } + } + [Fact] public static void PositiveInfinity() { diff --git a/src/libraries/System.Runtime/tests/System/UInt128Tests.cs b/src/libraries/System.Runtime/tests/System/UInt128Tests.cs index bbf445165daf4..f8f0939aac4d2 100644 --- a/src/libraries/System.Runtime/tests/System/UInt128Tests.cs +++ b/src/libraries/System.Runtime/tests/System/UInt128Tests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; using System.Numerics; +using System.Text; using Xunit; namespace System.Tests @@ -426,6 +427,49 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP } } + [Theory] + [MemberData(nameof(Parse_ValidWithOffsetCount_TestData))] + public static void Parse_Utf8Span_Valid(string value, int offset, int count, NumberStyles style, IFormatProvider provider, UInt128 expected) + { + UInt128 result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value, offset, count); + + // Default style and provider + if (style == NumberStyles.Integer && provider == null) + { + Assert.True(UInt128.TryParse(valueUtf8, out result)); + Assert.Equal(expected, result); + } + + Assert.Equal(expected, UInt128.Parse(valueUtf8, style, provider)); + + Assert.True(UInt128.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value != null) + { + UInt128 result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value); + + // Default style and provider + if (style == NumberStyles.Integer && provider == null) + { + Assert.False(UInt128.TryParse(valueUtf8, out result)); + Assert.Equal(0u, result); + } + + Assert.Throws(exceptionType, () => UInt128.Parse(valueUtf8, style, provider)); + + Assert.False(UInt128.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(0u, result); + } + } + [Theory] [MemberData(nameof(ToString_TestData))] public static void TryFormat(UInt128 i, string format, IFormatProvider provider, string expected) => diff --git a/src/libraries/System.Runtime/tests/System/UInt16Tests.cs b/src/libraries/System.Runtime/tests/System/UInt16Tests.cs index c66d6489e0cc2..4c161043d5798 100644 --- a/src/libraries/System.Runtime/tests/System/UInt16Tests.cs +++ b/src/libraries/System.Runtime/tests/System/UInt16Tests.cs @@ -366,6 +366,49 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP } } + [Theory] + [MemberData(nameof(Parse_ValidWithOffsetCount_TestData))] + public static void Parse_Utf8Span_Valid(string value, int offset, int count, NumberStyles style, IFormatProvider provider, ushort expected) + { + ushort result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value, offset, count); + + // Default style and provider + if (style == NumberStyles.Integer && provider == null) + { + Assert.True(ushort.TryParse(valueUtf8, out result)); + Assert.Equal(expected, result); + } + + Assert.Equal(expected, ushort.Parse(valueUtf8, style, provider)); + + Assert.True(ushort.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value != null) + { + ushort result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value); + + // Default style and provider + if (style == NumberStyles.Integer && provider == null) + { + Assert.False(ushort.TryParse(valueUtf8, out result)); + Assert.Equal(0u, result); + } + + Assert.Throws(exceptionType, () => ushort.Parse(valueUtf8, style, provider)); + + Assert.False(ushort.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(0u, result); + } + } + [Theory] [MemberData(nameof(ToString_TestData))] public static void TryFormat(ushort i, string format, IFormatProvider provider, string expected) => diff --git a/src/libraries/System.Runtime/tests/System/UInt32Tests.cs b/src/libraries/System.Runtime/tests/System/UInt32Tests.cs index 70ecde9691d68..50aa095d478c3 100644 --- a/src/libraries/System.Runtime/tests/System/UInt32Tests.cs +++ b/src/libraries/System.Runtime/tests/System/UInt32Tests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; using System.Numerics; +using System.Text; using Xunit; namespace System.Tests @@ -395,6 +396,49 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP } } + [Theory] + [MemberData(nameof(Parse_ValidWithOffsetCount_TestData))] + public static void Parse_Utf8Span_Valid(string value, int offset, int count, NumberStyles style, IFormatProvider provider, uint expected) + { + uint result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value, offset, count); + + // Default style and provider + if (style == NumberStyles.Integer && provider == null) + { + Assert.True(uint.TryParse(valueUtf8, out result)); + Assert.Equal(expected, result); + } + + Assert.Equal(expected, uint.Parse(valueUtf8, style, provider)); + + Assert.True(uint.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value != null) + { + uint result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value); + + // Default style and provider + if (style == NumberStyles.Integer && provider == null) + { + Assert.False(uint.TryParse(valueUtf8, out result)); + Assert.Equal(0u, result); + } + + Assert.Throws(exceptionType, () => uint.Parse(valueUtf8, style, provider)); + + Assert.False(uint.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(0u, result); + } + } + [Theory] [MemberData(nameof(ToString_TestData))] public static void TryFormat(uint i, string format, IFormatProvider provider, string expected) => diff --git a/src/libraries/System.Runtime/tests/System/UInt64Tests.cs b/src/libraries/System.Runtime/tests/System/UInt64Tests.cs index 07eaa91e3109d..d0f1234f57447 100644 --- a/src/libraries/System.Runtime/tests/System/UInt64Tests.cs +++ b/src/libraries/System.Runtime/tests/System/UInt64Tests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; using System.Numerics; +using System.Text; using Xunit; namespace System.Tests @@ -408,6 +409,49 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP } } + [Theory] + [MemberData(nameof(Parse_ValidWithOffsetCount_TestData))] + public static void Parse_Utf8Span_Valid(string value, int offset, int count, NumberStyles style, IFormatProvider provider, ulong expected) + { + ulong result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value, offset, count); + + // Default style and provider + if (style == NumberStyles.Integer && provider == null) + { + Assert.True(ulong.TryParse(valueUtf8, out result)); + Assert.Equal(expected, result); + } + + Assert.Equal(expected, ulong.Parse(valueUtf8, style, provider)); + + Assert.True(ulong.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value != null) + { + ulong result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value); + + // Default style and provider + if (style == NumberStyles.Integer && provider == null) + { + Assert.False(ulong.TryParse(valueUtf8, out result)); + Assert.Equal(0u, result); + } + + Assert.Throws(exceptionType, () => ulong.Parse(valueUtf8, style, provider)); + + Assert.False(ulong.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(0u, result); + } + } + [Theory] [MemberData(nameof(ToString_TestData))] public static void TryFormat(ulong i, string format, IFormatProvider provider, string expected) => diff --git a/src/libraries/System.Runtime/tests/System/UIntPtrTests.cs b/src/libraries/System.Runtime/tests/System/UIntPtrTests.cs index d8be368fb42e8..357a8bea81de2 100644 --- a/src/libraries/System.Runtime/tests/System/UIntPtrTests.cs +++ b/src/libraries/System.Runtime/tests/System/UIntPtrTests.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; +using System.Text; using Xunit; namespace System.Tests @@ -547,6 +548,49 @@ public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatP } } + [Theory] + [MemberData(nameof(Parse_ValidWithOffsetCount_TestData))] + public static void Parse_Utf8Span_Valid(string value, int offset, int count, NumberStyles style, IFormatProvider provider, nuint expected) + { + nuint result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value, offset, count); + + // Default style and provider + if (style == NumberStyles.Integer && provider == null) + { + Assert.True(nuint.TryParse(valueUtf8, out result)); + Assert.Equal(expected, result); + } + + Assert.Equal(expected, nuint.Parse(valueUtf8, style, provider)); + + Assert.True(nuint.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value != null) + { + nuint result; + ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value); + + // Default style and provider + if (style == NumberStyles.Integer && provider == null) + { + Assert.False(nuint.TryParse(valueUtf8, out result)); + Assert.Equal(default, result); + } + + Assert.Throws(exceptionType, () => nuint.Parse(valueUtf8, style, provider)); + + Assert.False(nuint.TryParse(valueUtf8, style, provider, out result)); + Assert.Equal(default, result); + } + } + [Theory] [MemberData(nameof(ToString_TestData))] public static void TryFormat(nuint i, string format, IFormatProvider provider, string expected) => From ba28bab213f78cdee4a276343eccde1c84f90511 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 29 May 2023 11:54:14 -0700 Subject: [PATCH 07/19] Ensure that tests don't try to capture a span in lambda --- src/libraries/System.Runtime/tests/System/ByteTests.cs | 2 +- src/libraries/System.Runtime/tests/System/DecimalTests.cs | 2 +- src/libraries/System.Runtime/tests/System/DoubleTests.cs | 2 +- src/libraries/System.Runtime/tests/System/HalfTests.cs | 2 +- src/libraries/System.Runtime/tests/System/Int128Tests.cs | 2 +- src/libraries/System.Runtime/tests/System/Int16Tests.cs | 2 +- src/libraries/System.Runtime/tests/System/Int32Tests.cs | 2 +- src/libraries/System.Runtime/tests/System/Int64Tests.cs | 2 +- src/libraries/System.Runtime/tests/System/IntPtrTests.cs | 2 +- src/libraries/System.Runtime/tests/System/SByteTests.cs | 2 +- src/libraries/System.Runtime/tests/System/SingleTests.cs | 2 +- src/libraries/System.Runtime/tests/System/UInt128Tests.cs | 2 +- src/libraries/System.Runtime/tests/System/UInt16Tests.cs | 2 +- src/libraries/System.Runtime/tests/System/UInt32Tests.cs | 2 +- src/libraries/System.Runtime/tests/System/UInt64Tests.cs | 2 +- src/libraries/System.Runtime/tests/System/UIntPtrTests.cs | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System/ByteTests.cs b/src/libraries/System.Runtime/tests/System/ByteTests.cs index ac05451bedb70..3593c9b4f470e 100644 --- a/src/libraries/System.Runtime/tests/System/ByteTests.cs +++ b/src/libraries/System.Runtime/tests/System/ByteTests.cs @@ -405,7 +405,7 @@ public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFor Assert.Equal(0u, result); } - Assert.Throws(exceptionType, () => byte.Parse(valueUtf8, style, provider)); + Assert.Throws(exceptionType, () => byte.Parse(Encoding.UTF8.GetBytes(value), style, provider)); Assert.False(byte.TryParse(valueUtf8, style, provider, out result)); Assert.Equal(0u, result); diff --git a/src/libraries/System.Runtime/tests/System/DecimalTests.cs b/src/libraries/System.Runtime/tests/System/DecimalTests.cs index 913f3ba13be87..a40541573c442 100644 --- a/src/libraries/System.Runtime/tests/System/DecimalTests.cs +++ b/src/libraries/System.Runtime/tests/System/DecimalTests.cs @@ -1037,7 +1037,7 @@ public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFor if (value != null) { ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value); - Assert.Throws(exceptionType, () => decimal.Parse(valueUtf8, style, provider)); + Assert.Throws(exceptionType, () => decimal.Parse(Encoding.UTF8.GetBytes(value), style, provider)); Assert.False(decimal.TryParse(valueUtf8, style, provider, out decimal result)); Assert.Equal(0, result); diff --git a/src/libraries/System.Runtime/tests/System/DoubleTests.cs b/src/libraries/System.Runtime/tests/System/DoubleTests.cs index 0f51597314d78..2b2b7fb5eee74 100644 --- a/src/libraries/System.Runtime/tests/System/DoubleTests.cs +++ b/src/libraries/System.Runtime/tests/System/DoubleTests.cs @@ -652,7 +652,7 @@ public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFor if (value != null) { ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value); - Assert.Throws(exceptionType, () => double.Parse(valueUtf8, style, provider)); + Assert.Throws(exceptionType, () => double.Parse(Encoding.UTF8.GetBytes(value), style, provider)); Assert.False(double.TryParse(valueUtf8, style, provider, out double result)); Assert.Equal(0, result); diff --git a/src/libraries/System.Runtime/tests/System/HalfTests.cs b/src/libraries/System.Runtime/tests/System/HalfTests.cs index 7c77877de4cd7..b37476cb9ae63 100644 --- a/src/libraries/System.Runtime/tests/System/HalfTests.cs +++ b/src/libraries/System.Runtime/tests/System/HalfTests.cs @@ -921,7 +921,7 @@ public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFor if (value != null) { ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value); - Assert.Throws(exceptionType, () => float.Parse(valueUtf8, style, provider)); + Assert.Throws(exceptionType, () => float.Parse(Encoding.UTF8.GetBytes(value), style, provider)); Assert.False(float.TryParse(valueUtf8, style, provider, out float result)); Assert.Equal(0, result); diff --git a/src/libraries/System.Runtime/tests/System/Int128Tests.cs b/src/libraries/System.Runtime/tests/System/Int128Tests.cs index 3eb302ac01b1d..26fba082e64a8 100644 --- a/src/libraries/System.Runtime/tests/System/Int128Tests.cs +++ b/src/libraries/System.Runtime/tests/System/Int128Tests.cs @@ -476,7 +476,7 @@ public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFor Assert.Equal(0, result); } - Assert.Throws(exceptionType, () => Int128.Parse(valueUtf8, style, provider)); + Assert.Throws(exceptionType, () => Int128.Parse(Encoding.UTF8.GetBytes(value), style, provider)); Assert.False(Int128.TryParse(valueUtf8, style, provider, out result)); Assert.Equal(0, result); diff --git a/src/libraries/System.Runtime/tests/System/Int16Tests.cs b/src/libraries/System.Runtime/tests/System/Int16Tests.cs index 4b8e8ad460b3f..150b035dccb5f 100644 --- a/src/libraries/System.Runtime/tests/System/Int16Tests.cs +++ b/src/libraries/System.Runtime/tests/System/Int16Tests.cs @@ -430,7 +430,7 @@ public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFor Assert.Equal(0, result); } - Assert.Throws(exceptionType, () => short.Parse(valueUtf8, style, provider)); + Assert.Throws(exceptionType, () => short.Parse(Encoding.UTF8.GetBytes(value), style, provider)); Assert.False(short.TryParse(valueUtf8, style, provider, out result)); Assert.Equal(0, result); diff --git a/src/libraries/System.Runtime/tests/System/Int32Tests.cs b/src/libraries/System.Runtime/tests/System/Int32Tests.cs index 836dbdf4718b1..0b5f7c8250efe 100644 --- a/src/libraries/System.Runtime/tests/System/Int32Tests.cs +++ b/src/libraries/System.Runtime/tests/System/Int32Tests.cs @@ -864,7 +864,7 @@ public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFor Assert.Equal(0, result); } - Assert.Throws(exceptionType, () => int.Parse(valueUtf8, style, provider)); + Assert.Throws(exceptionType, () => int.Parse(Encoding.UTF8.GetBytes(value), style, provider)); Assert.False(int.TryParse(valueUtf8, style, provider, out result)); Assert.Equal(0, result); diff --git a/src/libraries/System.Runtime/tests/System/Int64Tests.cs b/src/libraries/System.Runtime/tests/System/Int64Tests.cs index fa1e69bf8d08a..07b79d9433300 100644 --- a/src/libraries/System.Runtime/tests/System/Int64Tests.cs +++ b/src/libraries/System.Runtime/tests/System/Int64Tests.cs @@ -458,7 +458,7 @@ public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFor Assert.Equal(0, result); } - Assert.Throws(exceptionType, () => long.Parse(valueUtf8, style, provider)); + Assert.Throws(exceptionType, () => long.Parse(Encoding.UTF8.GetBytes(value), style, provider)); Assert.False(long.TryParse(valueUtf8, style, provider, out result)); Assert.Equal(0, result); diff --git a/src/libraries/System.Runtime/tests/System/IntPtrTests.cs b/src/libraries/System.Runtime/tests/System/IntPtrTests.cs index 48cf51d8dfc14..f4a16159467f7 100644 --- a/src/libraries/System.Runtime/tests/System/IntPtrTests.cs +++ b/src/libraries/System.Runtime/tests/System/IntPtrTests.cs @@ -1021,7 +1021,7 @@ public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFor Assert.Equal(default, result); } - Assert.Throws(exceptionType, () => int.Parse(valueUtf8, style, provider)); + Assert.Throws(exceptionType, () => int.Parse(Encoding.UTF8.GetBytes(value), style, provider)); Assert.False(nint.TryParse(valueUtf8, style, provider, out result)); Assert.Equal(default, result); diff --git a/src/libraries/System.Runtime/tests/System/SByteTests.cs b/src/libraries/System.Runtime/tests/System/SByteTests.cs index 328ea50d71cea..0f18bb2e61e02 100644 --- a/src/libraries/System.Runtime/tests/System/SByteTests.cs +++ b/src/libraries/System.Runtime/tests/System/SByteTests.cs @@ -424,7 +424,7 @@ public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFor Assert.Equal(0, result); } - Assert.Throws(exceptionType, () => sbyte.Parse(valueUtf8, style, provider)); + Assert.Throws(exceptionType, () => sbyte.Parse(Encoding.UTF8.GetBytes(value), style, provider)); Assert.False(sbyte.TryParse(valueUtf8, style, provider, out result)); Assert.Equal(0, result); diff --git a/src/libraries/System.Runtime/tests/System/SingleTests.cs b/src/libraries/System.Runtime/tests/System/SingleTests.cs index 122a142314d0b..20e168e81c7c8 100644 --- a/src/libraries/System.Runtime/tests/System/SingleTests.cs +++ b/src/libraries/System.Runtime/tests/System/SingleTests.cs @@ -591,7 +591,7 @@ public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFor if (value != null) { ReadOnlySpan valueUtf8 = Encoding.UTF8.GetBytes(value); - Assert.Throws(exceptionType, () => float.Parse(valueUtf8, style, provider)); + Assert.Throws(exceptionType, () => float.Parse(Encoding.UTF8.GetBytes(value), style, provider)); Assert.False(float.TryParse(valueUtf8, style, provider, out float result)); Assert.Equal(0, result); diff --git a/src/libraries/System.Runtime/tests/System/UInt128Tests.cs b/src/libraries/System.Runtime/tests/System/UInt128Tests.cs index f8f0939aac4d2..f17444e678da0 100644 --- a/src/libraries/System.Runtime/tests/System/UInt128Tests.cs +++ b/src/libraries/System.Runtime/tests/System/UInt128Tests.cs @@ -463,7 +463,7 @@ public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFor Assert.Equal(0u, result); } - Assert.Throws(exceptionType, () => UInt128.Parse(valueUtf8, style, provider)); + Assert.Throws(exceptionType, () => UInt128.Parse(Encoding.UTF8.GetBytes(value), style, provider)); Assert.False(UInt128.TryParse(valueUtf8, style, provider, out result)); Assert.Equal(0u, result); diff --git a/src/libraries/System.Runtime/tests/System/UInt16Tests.cs b/src/libraries/System.Runtime/tests/System/UInt16Tests.cs index 4c161043d5798..25b4c3b944678 100644 --- a/src/libraries/System.Runtime/tests/System/UInt16Tests.cs +++ b/src/libraries/System.Runtime/tests/System/UInt16Tests.cs @@ -402,7 +402,7 @@ public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFor Assert.Equal(0u, result); } - Assert.Throws(exceptionType, () => ushort.Parse(valueUtf8, style, provider)); + Assert.Throws(exceptionType, () => ushort.Parse(Encoding.UTF8.GetBytes(value), style, provider)); Assert.False(ushort.TryParse(valueUtf8, style, provider, out result)); Assert.Equal(0u, result); diff --git a/src/libraries/System.Runtime/tests/System/UInt32Tests.cs b/src/libraries/System.Runtime/tests/System/UInt32Tests.cs index 50aa095d478c3..f912fbd03434a 100644 --- a/src/libraries/System.Runtime/tests/System/UInt32Tests.cs +++ b/src/libraries/System.Runtime/tests/System/UInt32Tests.cs @@ -432,7 +432,7 @@ public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFor Assert.Equal(0u, result); } - Assert.Throws(exceptionType, () => uint.Parse(valueUtf8, style, provider)); + Assert.Throws(exceptionType, () => uint.Parse(Encoding.UTF8.GetBytes(value), style, provider)); Assert.False(uint.TryParse(valueUtf8, style, provider, out result)); Assert.Equal(0u, result); diff --git a/src/libraries/System.Runtime/tests/System/UInt64Tests.cs b/src/libraries/System.Runtime/tests/System/UInt64Tests.cs index d0f1234f57447..e47286dca51b2 100644 --- a/src/libraries/System.Runtime/tests/System/UInt64Tests.cs +++ b/src/libraries/System.Runtime/tests/System/UInt64Tests.cs @@ -445,7 +445,7 @@ public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFor Assert.Equal(0u, result); } - Assert.Throws(exceptionType, () => ulong.Parse(valueUtf8, style, provider)); + Assert.Throws(exceptionType, () => ulong.Parse(Encoding.UTF8.GetBytes(value), style, provider)); Assert.False(ulong.TryParse(valueUtf8, style, provider, out result)); Assert.Equal(0u, result); diff --git a/src/libraries/System.Runtime/tests/System/UIntPtrTests.cs b/src/libraries/System.Runtime/tests/System/UIntPtrTests.cs index 357a8bea81de2..6e1f5d3b46094 100644 --- a/src/libraries/System.Runtime/tests/System/UIntPtrTests.cs +++ b/src/libraries/System.Runtime/tests/System/UIntPtrTests.cs @@ -584,7 +584,7 @@ public static void Parse_Utf8Span_Invalid(string value, NumberStyles style, IFor Assert.Equal(default, result); } - Assert.Throws(exceptionType, () => nuint.Parse(valueUtf8, style, provider)); + Assert.Throws(exceptionType, () => nuint.Parse(Encoding.UTF8.GetBytes(value), style, provider)); Assert.False(nuint.TryParse(valueUtf8, style, provider, out result)); Assert.Equal(default, result); From 0fdcd4eb438645a48add1a3bf943648edab3573c Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 29 May 2023 13:07:50 -0700 Subject: [PATCH 08/19] Ensure that MatchChars does the right thing --- .../System.Private.CoreLib/src/System/Number.Parsing.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d7ca1e866a51a..c5a1dd2b6a83b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -1350,7 +1350,7 @@ private static bool TrailingZeros(ReadOnlySpan value, int index) p++; str++; - if (val == '\0') + if (TChar.CastToUInt32(*str) == '\0') { return p; } From 9e1fe3e4e4682a3a073117e2a678464a25e1f999 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 29 May 2023 13:28:55 -0700 Subject: [PATCH 09/19] Account for the switch from string to ROSpan for currSymbol --- .../src/System/Number.Parsing.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 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 c5a1dd2b6a83b..32980ce22893d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -237,10 +237,10 @@ private static unsafe bool TryParseNumber(scoped ref TChar* str, TChar* s state |= StateSign | StateParens; number.IsNegative = true; } - else if (currSymbol != null && (next = MatchChars(p, strEnd, currSymbol)) != null) + else if (!currSymbol.IsEmpty && (next = MatchChars(p, strEnd, currSymbol)) != null) { state |= StateCurrency; - currSymbol = null; + currSymbol = ReadOnlySpan.Empty; // We already found the currency symbol. There should not be more currency symbols. Set // currSymbol to NULL so that we won't search it again in the later code path. p = next - 1; @@ -400,9 +400,9 @@ private static unsafe bool TryParseNumber(scoped ref TChar* str, TChar* s { state &= ~StateParens; } - else if (currSymbol != null && (next = MatchChars(p, strEnd, currSymbol)) != null) + else if (!currSymbol.IsEmpty && (next = MatchChars(p, strEnd, currSymbol)) != null) { - currSymbol = null; + currSymbol = ReadOnlySpan.Empty; p = next - 1; } else @@ -1328,7 +1328,7 @@ private static bool TrailingZeros(ReadOnlySpan value, int index) { Debug.Assert((p != null) && (pEnd != null) && (p <= pEnd) && (value != null)); - fixed (TChar* stringPointer = value) + fixed (TChar* stringPointer = &MemoryMarshal.GetReference(value)) { TChar* str = stringPointer; From 6c3dfdac4477881bd57b46410ceab3bbef23041a Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 29 May 2023 17:27:16 -0700 Subject: [PATCH 10/19] Ensure EqualsIgnoreCaseUtf8_Scalar handles the remaining elements correctly --- .../src/System/Globalization/Ordinal.Utf8.cs | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs index 4c5d10bc8f3c1..47bd694457649 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs @@ -164,16 +164,16 @@ internal static bool EqualsIgnoreCaseUtf8_Scalar(ref byte charA, ref byte charB, } byteOffset += 8; - length -= 4; + length -= 8; } #endif uint valueAu32 = 0; uint valueBu32 = 0; - // Read 2 chars (32 bits) at a time from each string + // Read 4 chars (32 bits) at a time from each string #if TARGET_64BIT - if ((uint)length >= 2) + if ((uint)length >= 4) #else - while ((uint)length >= 2) + while ((uint)length >= 4) #endif { valueAu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charA, byteOffset)); @@ -197,17 +197,24 @@ internal static bool EqualsIgnoreCaseUtf8_Scalar(ref byte charA, ref byte charB, } byteOffset += 4; - length -= 2; + length -= 4; } if (length != 0) { - Debug.Assert(length == 1); + // We have 1, 2, or 3 bytes remaining. We want to backtrack + // so we read exactly 4 bytes and then do one final iteration. + + Debug.Assert(length <= 3); + int backtrack = 4 - length; - valueAu32 = Unsafe.AddByteOffset(ref charA, byteOffset); - valueBu32 = Unsafe.AddByteOffset(ref charB, byteOffset); + length += backtrack; + byteOffset -= backtrack; + + valueAu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charA, byteOffset)); + valueBu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charB, byteOffset)); - if ((valueAu32 | valueBu32) > 0x7Fu) + if (!Utf8Utility.AllBytesInUInt32AreAscii(valueAu32 | valueBu32)) { goto NonAscii32; // one of the inputs contains non-ASCII data } @@ -217,13 +224,13 @@ internal static bool EqualsIgnoreCaseUtf8_Scalar(ref byte charA, ref byte charB, return true; // exact match } - valueAu32 |= 0x20u; - if ((uint)(valueAu32 - 'a') > (uint)('z' - 'a')) + if (!Utf8Utility.UInt32OrdinalIgnoreCaseAscii(valueAu32, valueBu32)) { - return false; // not exact match, and first input isn't in [A-Za-z] + return false; } - return valueAu32 == (valueBu32 | 0x20u); + byteOffset += 4; + length -= 4; } Debug.Assert(length == 0); From 5056ac8cfd6076b93e4c682afbebca0f16f1bb4c Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 7 Jun 2023 07:00:23 -0700 Subject: [PATCH 11/19] Allow alloc-free conversion for the UTF8 parsing fallback paths --- .../System/Globalization/CompareInfo.Utf8.cs | 60 +++++++- .../src/System/Globalization/Ordinal.Utf8.cs | 64 +++++++- .../src/System/Numerics/INumberBase.cs | 137 +++++++++++++++++- 3 files changed, 245 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Utf8.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Utf8.cs index 789d764564646..b6c09642f8687 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Utf8.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Utf8.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; using System.Text; namespace System.Globalization @@ -74,10 +75,63 @@ private unsafe bool StartsWithCoreUtf8(ReadOnlySpan source, ReadOnlySpan sourceUtf16 = Encoding.Unicode.GetString(source); - ReadOnlySpan prefixUtf16 = Encoding.Unicode.GetString(prefix); + // Convert source using stackalloc for <= 256 characters and ArrayPool otherwise - return StartsWithCore(sourceUtf16, prefixUtf16, options, matchLengthPtr: null); + char[]? sourceUtf16Array; + scoped Span sourceUtf16; + int sourceMaxCharCount = Encoding.UTF8.GetMaxCharCount(source.Length); + + if (sourceMaxCharCount <= 256) + { + sourceUtf16Array = null; + sourceUtf16 = stackalloc char[512]; + } + else + { + sourceUtf16Array = ArrayPool.Shared.Rent(sourceMaxCharCount); + sourceUtf16 = sourceUtf16Array.AsSpan(0, sourceMaxCharCount); + } + + int sourceUtf16Length = Encoding.UTF8.GetChars(source, sourceUtf16); + sourceUtf16 = sourceUtf16.Slice(0, sourceUtf16Length); + + // Convert prefix using stackalloc for <= 256 characters and ArrayPool otherwise + + char[]? prefixUtf16Array; + scoped Span prefixUtf16; + int prefixMaxCharCount = Encoding.UTF8.GetMaxCharCount(prefix.Length); + + if (prefixMaxCharCount < 256) + { + prefixUtf16Array = null; + prefixUtf16 = stackalloc char[512]; + } + else + { + prefixUtf16Array = ArrayPool.Shared.Rent(prefixMaxCharCount); + prefixUtf16 = prefixUtf16Array.AsSpan(0, prefixMaxCharCount); + } + + int prefixUtf16Length = Encoding.UTF8.GetChars(prefix, prefixUtf16); + prefixUtf16 = prefixUtf16.Slice(0, prefixUtf16Length); + + // Actual operation + + bool result = StartsWithCore(sourceUtf16, prefixUtf16, options, matchLengthPtr: null); + + // Return rented buffers if necessary + + if (prefixUtf16Array != null) + { + ArrayPool.Shared.Return(prefixUtf16Array); + } + + if (sourceUtf16Array != null) + { + ArrayPool.Shared.Return(sourceUtf16Array); + } + + return result; } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs index 47bd694457649..375253f2af343 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -66,13 +67,66 @@ internal static int CompareStringIgnoreCaseNonAsciiUtf8(ref byte strA, int lengt { // NLS/ICU doesn't provide native UTF-8 support so we need to convert to UTF-16 and compare that way - ReadOnlySpan stringAUtf16 = Encoding.Unicode.GetString(MemoryMarshal.CreateReadOnlySpan(ref strA, lengthA)); - ReadOnlySpan stringBUtf16 = Encoding.Unicode.GetString(MemoryMarshal.CreateReadOnlySpan(ref strB, lengthB)); + // Convert strA using stackalloc for <= 256 characters and ArrayPool otherwise - return CompareStringIgnoreCaseNonAscii( - ref MemoryMarshal.GetReference(stringAUtf16), stringAUtf16.Length, - ref MemoryMarshal.GetReference(stringBUtf16), stringBUtf16.Length + char[]? strAUtf16Array; + scoped Span strAUtf16; + int strAMaxCharCount = Encoding.UTF8.GetMaxCharCount(lengthA); + + if (strAMaxCharCount <= 256) + { + strAUtf16Array = null; + strAUtf16 = stackalloc char[512]; + } + else + { + strAUtf16Array = ArrayPool.Shared.Rent(strAMaxCharCount); + strAUtf16 = strAUtf16Array.AsSpan(0, strAMaxCharCount); + } + + int strAUtf16Length = Encoding.UTF8.GetChars(MemoryMarshal.CreateReadOnlySpan(ref strA, lengthA), strAUtf16); + strAUtf16 = strAUtf16.Slice(0, strAUtf16Length); + + // Convert strB using stackalloc for <= 256 characters and ArrayPool otherwise + + char[]? strBUtf16Array; + scoped Span strBUtf16; + int strBMaxCharCount = Encoding.UTF8.GetMaxCharCount(lengthB); + + if (strBMaxCharCount <= 256) + { + strBUtf16Array = null; + strBUtf16 = stackalloc char[512]; + } + else + { + strBUtf16Array = ArrayPool.Shared.Rent(strBMaxCharCount); + strBUtf16 = strBUtf16Array.AsSpan(0, strBMaxCharCount); + } + + int strBUtf16Length = Encoding.UTF8.GetChars(MemoryMarshal.CreateReadOnlySpan(ref strB, lengthB), strBUtf16); + strBUtf16 = strBUtf16.Slice(0, strBUtf16Length); + + // Actual operation + + int result = CompareStringIgnoreCaseNonAscii( + ref MemoryMarshal.GetReference(strAUtf16), strAUtf16.Length, + ref MemoryMarshal.GetReference(strBUtf16), strBUtf16.Length ); + + // Return rented buffers if necessary + + if (strBUtf16Array != null) + { + ArrayPool.Shared.Return(strBUtf16Array); + } + + if (strAUtf16Array != null) + { + ArrayPool.Shared.Return(strAUtf16Array); + } + + return result; } private static bool EqualsIgnoreCaseUtf8_Vector128(ref byte charA, ref byte charB, int length) 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 d2858a237f671..da6a5aaf63849 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.CompilerServices; @@ -286,8 +287,38 @@ static virtual TSelf CreateTruncating(TOther value) /// is not representable by . static virtual TSelf Parse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider) { - string s = Encoding.Unicode.GetString(utf8Text); - return TSelf.Parse(s, style, provider); + // Convert text using stackalloc for <= 256 characters and ArrayPool otherwise + + char[]? utf16TextArray; + scoped Span utf16Text; + int textMaxCharCount = Encoding.UTF8.GetMaxCharCount(utf8Text.Length); + + if (textMaxCharCount < 256) + { + utf16TextArray = null; + utf16Text = stackalloc char[512]; + } + else + { + utf16TextArray = ArrayPool.Shared.Rent(textMaxCharCount); + utf16Text = utf16TextArray.AsSpan(0, textMaxCharCount); + } + + int utf16TextLength = Encoding.UTF8.GetChars(utf8Text, utf16Text); + utf16Text = utf16Text.Slice(0, utf16TextLength); + + // Actual operation + + TSelf result = TSelf.Parse(utf16Text, style, provider); + + // Return rented buffers if necessary + + if (utf16TextArray != null) + { + ArrayPool.Shared.Return(utf16TextArray); + } + + return result; } /// Tries to convert a value to an instance of the current type, throwing an overflow exception for any values that fall outside the representable range of the current type. @@ -379,20 +410,110 @@ protected static abstract bool TryConvertToTruncating(TSelf value, [Mayb /// is not a supported value. static virtual bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out TSelf result) { - string s = Encoding.Unicode.GetString(utf8Text); - return TSelf.TryParse(s, style, provider, out result); + // Convert text using stackalloc for <= 256 characters and ArrayPool otherwise + + char[]? utf16TextArray; + scoped Span utf16Text; + int textMaxCharCount = Encoding.UTF8.GetMaxCharCount(utf8Text.Length); + + if (textMaxCharCount < 256) + { + utf16TextArray = null; + utf16Text = stackalloc char[512]; + } + else + { + utf16TextArray = ArrayPool.Shared.Rent(textMaxCharCount); + utf16Text = utf16TextArray.AsSpan(0, textMaxCharCount); + } + + int utf16TextLength = Encoding.UTF8.GetChars(utf8Text, utf16Text); + utf16Text = utf16Text.Slice(0, utf16TextLength); + + // Actual operation + + bool succeeded = TSelf.TryParse(utf16Text, style, provider, out result); + + // Return rented buffers if necessary + + if (utf16TextArray != null) + { + ArrayPool.Shared.Return(utf16TextArray); + } + + return succeeded; } static TSelf IUtf8SpanParsable.Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) { - string s = Encoding.Unicode.GetString(utf8Text); - return TSelf.Parse(s, provider); + // Convert text using stackalloc for <= 256 characters and ArrayPool otherwise + + char[]? utf16TextArray; + scoped Span utf16Text; + int textMaxCharCount = Encoding.UTF8.GetMaxCharCount(utf8Text.Length); + + if (textMaxCharCount < 256) + { + utf16TextArray = null; + utf16Text = stackalloc char[512]; + } + else + { + utf16TextArray = ArrayPool.Shared.Rent(textMaxCharCount); + utf16Text = utf16TextArray.AsSpan(0, textMaxCharCount); + } + + int utf16TextLength = Encoding.UTF8.GetChars(utf8Text, utf16Text); + utf16Text = utf16Text.Slice(0, utf16TextLength); + + // Actual operation + + TSelf result = TSelf.Parse(utf16Text, provider); + + // Return rented buffers if necessary + + if (utf16TextArray != null) + { + ArrayPool.Shared.Return(utf16TextArray); + } + + return result; } static bool IUtf8SpanParsable.TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, [MaybeNullWhen(returnValue: false)] out TSelf result) { - string s = Encoding.Unicode.GetString(utf8Text); - return TSelf.TryParse(s, provider, out result); + // Convert text using stackalloc for <= 256 characters and ArrayPool otherwise + + char[]? utf16TextArray; + scoped Span utf16Text; + int textMaxCharCount = Encoding.UTF8.GetMaxCharCount(utf8Text.Length); + + if (textMaxCharCount < 256) + { + utf16TextArray = null; + utf16Text = stackalloc char[512]; + } + else + { + utf16TextArray = ArrayPool.Shared.Rent(textMaxCharCount); + utf16Text = utf16TextArray.AsSpan(0, textMaxCharCount); + } + + int utf16TextLength = Encoding.UTF8.GetChars(utf8Text, utf16Text); + utf16Text = utf16Text.Slice(0, utf16TextLength); + + // Actual operation + + bool succeeded = TSelf.TryParse(utf16Text, provider, out result); + + // Return rented buffers if necessary + + if (utf16TextArray != null) + { + ArrayPool.Shared.Return(utf16TextArray); + } + + return succeeded; } } } From cb9bef0dd7a65632bde0ed5a6e76dc2a7a532cad Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 7 Jun 2023 08:16:27 -0700 Subject: [PATCH 12/19] Remove unnecessary attributes and ensure trailing elements are correctly handled --- .../src/System/Globalization/Ordinal.Utf8.cs | 33 ++++++++++++++----- .../MemoryExtensions.Globalization.Utf8.cs | 1 - .../src/System/MemoryExtensions.Trim.Utf8.cs | 1 - 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs index 375253f2af343..0a0dec8ba3f1c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs @@ -256,17 +256,34 @@ internal static bool EqualsIgnoreCaseUtf8_Scalar(ref byte charA, ref byte charB, if (length != 0) { - // We have 1, 2, or 3 bytes remaining. We want to backtrack - // so we read exactly 4 bytes and then do one final iteration. + // We have 1, 2, or 3 bytes remaining. We can't do anything fancy + // like backtracking since we could have only had 1-3 bytes. So, + // instead we'll do 1 or 2 reads to get all 3 bytes. Endianness + // doesn't matter here since we only compare if all bytes are ascii + // and the ordering will be consistent between the two comparisons - Debug.Assert(length <= 3); - int backtrack = 4 - length; + if (length == 3) + { + valueAu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charA, byteOffset)); + valueBu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charB, byteOffset)); - length += backtrack; - byteOffset -= backtrack; + byteOffset += 2; - valueAu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charA, byteOffset)); - valueBu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charB, byteOffset)); + valueAu32 |= (uint)(Unsafe.AddByteOffset(ref charA, byteOffset) << 16); + valueBu32 |= (uint)(Unsafe.AddByteOffset(ref charB, byteOffset) << 16); + } + else if (length == 2) + { + valueAu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charA, byteOffset)); + valueBu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charB, byteOffset)); + } + else + { + Debug.Assert(length == 1); + + valueAu32 = Unsafe.AddByteOffset(ref charA, byteOffset); + valueBu32 = Unsafe.AddByteOffset(ref charB, byteOffset); + } if (!Utf8Utility.AllBytesInUInt32AreAscii(valueAu32 | valueBu32)) { diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.Utf8.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.Utf8.cs index fdec0bab4f6e5..30c61d99a3135 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.Utf8.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.Utf8.cs @@ -32,7 +32,6 @@ internal static bool EqualsOrdinalIgnoreCaseUtf8(this ReadOnlySpan span, R /// The source span. /// The sequence to compare to the beginning of the source span. /// One of the enumeration values that determines how the and are compared. - [Intrinsic] // Unrolled and vectorized for half-constant input (Ordinal) internal static bool StartsWithUtf8(this ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) { string.CheckStringComparison(comparisonType); diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Trim.Utf8.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Trim.Utf8.cs index 0f1c3b6da08d4..03c7acfcb2b8e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Trim.Utf8.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Trim.Utf8.cs @@ -9,7 +9,6 @@ namespace System { public static partial class MemoryExtensions { - [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ReadOnlySpan TrimUtf8(this ReadOnlySpan span) { // Assume that in most cases input doesn't need trimming From e611907daca4dd0bba2a972f1a3c560b8135e443 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 14 Jun 2023 11:23:57 -0700 Subject: [PATCH 13/19] Fix the handling of UTF8 as per the feedback --- .../System/Globalization/CompareInfo.Utf8.cs | 15 +- .../src/System/Globalization/Ordinal.Utf8.cs | 485 +++++++++++++++--- .../MemoryExtensions.Globalization.Utf8.cs | 21 +- 3 files changed, 427 insertions(+), 94 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Utf8.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Utf8.cs index b6c09642f8687..f72147a48c34e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Utf8.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Utf8.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Text; +using System.Text.Unicode; namespace System.Globalization { @@ -92,7 +93,12 @@ private unsafe bool StartsWithCoreUtf8(ReadOnlySpan source, ReadOnlySpan source, ReadOnlySpan spanA = MemoryMarshal.CreateReadOnlySpan(ref strA, lengthA); + ReadOnlySpan spanB = MemoryMarshal.CreateReadOnlySpan(ref strB, lengthB); + + do + { + OperationStatus statusA = Rune.DecodeFromUtf8(spanA, out Rune runeA, out int bytesConsumedA); + OperationStatus statusB = Rune.DecodeFromUtf8(spanB, out Rune runeB, out int bytesConsumedB); + + if (statusA != statusB) + { + // OperationStatus don't match; fail immediately + return false; + } + + if (statusA == OperationStatus.Done) + { + if (Rune.ToUpperInvariant(runeA) != Rune.ToUpperInvariant(runeB)) { - currentB -= 0x20; + // Runes don't match when ignoring case; fail immediately + return false; } + } + else if (!spanA.Slice(0, bytesConsumedA).SequenceEqual(spanB.Slice(0, bytesConsumedB))) + { + // OperationStatus match, but bytesConsumed or the sequence of bytes consumed do not; fail immediately + return false; + } + + // The current runes or invalid byte sequences matched, slice and continue. + // We'll exit the loop when the entirety of both spans have been processed. + // + // In the scenario where one buffer is empty before the other, we'll end up + // with that span returning OperationStatus.NeedsMoreData and bytesConsumed=0 + // while the other span will return a different OperationStatus or different + // bytesConsumed and thus fail the operation. - // Return the (case-insensitive) difference between them. - return currentA - currentB; + spanA = spanA.Slice(bytesConsumedA); + spanB = spanB.Slice(bytesConsumedB); + } + while ((spanA.Length | spanB.Length) != 0); + + return true; + } + + private static bool EqualsIgnoreCaseUtf8_Vector128(ref byte charA, int lengthA, ref byte charB, int lengthB) + { + Debug.Assert(lengthA >= Vector128.Count); + Debug.Assert(lengthB >= Vector128.Count); + Debug.Assert(Vector128.IsHardwareAccelerated); + + nuint lengthU = Math.Min((uint)lengthA, (uint)lengthB); + nuint lengthToExamine = lengthU - (nuint)Vector128.Count; + + nuint i = 0; + + Vector128 vec1; + Vector128 vec2; + + do + { + vec1 = Vector128.LoadUnsafe(ref charA, i); + vec2 = Vector128.LoadUnsafe(ref charB, i); + + if (!Utf8Utility.AllBytesInVector128AreAscii(vec1 | vec2)) + { + goto NON_ASCII; } + + if (!Utf8Utility.Vector128OrdinalIgnoreCaseAscii(vec1, vec2)) + { + return false; + } + + i += (nuint)Vector128.Count; } + while (i <= lengthToExamine); - if (length == 0) + if (i == lengthU) { - return lengthA - lengthB; + // success if we reached the end of both sequences + return lengthA == lengthB; } - range -= length; + // Use scalar path for trailing elements + return EqualsIgnoreCaseUtf8_Scalar(ref Unsafe.Add(ref charA, i), (int)(lengthU - i), ref Unsafe.Add(ref charB, i), (int)(lengthU - i)); + + NON_ASCII: + if (Utf8Utility.AllBytesInVector128AreAscii(vec1) || Utf8Utility.AllBytesInVector128AreAscii(vec2)) + { + // No need to use the fallback if one of the inputs is full-ASCII + return false; + } + + // Fallback for Non-ASCII inputs + return EqualsStringIgnoreCaseUtf8( + ref Unsafe.Add(ref charA, i), lengthA - (int)i, + ref Unsafe.Add(ref charB, i), lengthB - (int)i + ); + } - return CompareStringIgnoreCaseNonAsciiUtf8(ref charA, lengthA - range, ref charB, lengthB - range); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool EqualsIgnoreCaseUtf8(ref byte charA, int lengthA, ref byte charB, int lengthB) + { + if (!Vector128.IsHardwareAccelerated || (lengthA < Vector128.Count) || (lengthB < Vector128.Count)) + { + return EqualsIgnoreCaseUtf8_Scalar(ref charA, lengthA, ref charB, lengthB); + } + + return EqualsIgnoreCaseUtf8_Vector128(ref charA, lengthA, ref charB, lengthB); } - internal static int CompareStringIgnoreCaseNonAsciiUtf8(ref byte strA, int lengthA, ref byte strB, int lengthB) + internal static bool EqualsIgnoreCaseUtf8_Scalar(ref byte charA, int lengthA, ref byte charB, int lengthB) { - // NLS/ICU doesn't provide native UTF-8 support so we need to convert to UTF-16 and compare that way + IntPtr byteOffset = IntPtr.Zero; - // Convert strA using stackalloc for <= 256 characters and ArrayPool otherwise +#if TARGET_64BIT + int length = Math.Min(lengthA, lengthB); + int range = length; - char[]? strAUtf16Array; - scoped Span strAUtf16; - int strAMaxCharCount = Encoding.UTF8.GetMaxCharCount(lengthA); + ulong valueAu64 = 0; + ulong valueBu64 = 0; - if (strAMaxCharCount <= 256) + // Read 8 chars (64 bits) at a time from each string + while ((uint)length >= 8) { - strAUtf16Array = null; - strAUtf16 = stackalloc char[512]; + valueAu64 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charA, byteOffset)); + valueBu64 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charB, byteOffset)); + + // A 32-bit test - even with the bit-twiddling here - is more efficient than a 64-bit test. + ulong temp = valueAu64 | valueBu64; + + if (!Utf8Utility.AllBytesInUInt32AreAscii((uint)temp | (uint)(temp >> 32))) + { + // one of the inputs contains non-ASCII data + goto NonAscii64; + } + + // Generally, the caller has likely performed a first-pass check that the input strings + // are likely equal. Consider a dictionary which computes the hash code of its key before + // performing a proper deep equality check of the string contents. We want to optimize for + // the case where the equality check is likely to succeed, which means that we want to avoid + // branching within this loop unless we're about to exit the loop, either due to failure or + // due to us running out of input data. + + if (!Utf8Utility.UInt64OrdinalIgnoreCaseAscii(valueAu64, valueBu64)) + { + return false; + } + + byteOffset += 8; + length -= 8; } - else +#endif + + uint valueAu32 = 0; + uint valueBu32 = 0; + + // Read 4 chars (32 bits) at a time from each string +#if TARGET_64BIT + if ((uint)length >= 4) +#else + while ((uint)length >= 4) +#endif { - strAUtf16Array = ArrayPool.Shared.Rent(strAMaxCharCount); - strAUtf16 = strAUtf16Array.AsSpan(0, strAMaxCharCount); + valueAu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charA, byteOffset)); + valueBu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charB, byteOffset)); + + if (!Utf8Utility.AllBytesInUInt32AreAscii(valueAu32 | valueBu32)) + { + // one of the inputs contains non-ASCII data + goto NonAscii32; + } + + // Generally, the caller has likely performed a first-pass check that the input strings + // are likely equal. Consider a dictionary which computes the hash code of its key before + // performing a proper deep equality check of the string contents. We want to optimize for + // the case where the equality check is likely to succeed, which means that we want to avoid + // branching within this loop unless we're about to exit the loop, either due to failure or + // due to us running out of input data. + + if (!Utf8Utility.UInt32OrdinalIgnoreCaseAscii(valueAu32, valueBu32)) + { + return false; + } + + byteOffset += 4; + length -= 4; } - int strAUtf16Length = Encoding.UTF8.GetChars(MemoryMarshal.CreateReadOnlySpan(ref strA, lengthA), strAUtf16); - strAUtf16 = strAUtf16.Slice(0, strAUtf16Length); + if (length != 0) + { + // We have 1, 2, or 3 bytes remaining. We can't do anything fancy + // like backtracking since we could have only had 1-3 bytes. So, + // instead we'll do 1 or 2 reads to get all 3 bytes. Endianness + // doesn't matter here since we only compare if all bytes are ascii + // and the ordering will be consistent between the two comparisons + + if (length == 3) + { + valueAu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charA, byteOffset)); + valueBu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charB, byteOffset)); + + byteOffset += 2; + + valueAu32 |= (uint)(Unsafe.AddByteOffset(ref charA, byteOffset) << 16); + valueBu32 |= (uint)(Unsafe.AddByteOffset(ref charB, byteOffset) << 16); + } + else if (length == 2) + { + valueAu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charA, byteOffset)); + valueBu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charB, byteOffset)); + } + else + { + Debug.Assert(length == 1); + + valueAu32 = Unsafe.AddByteOffset(ref charA, byteOffset); + valueBu32 = Unsafe.AddByteOffset(ref charB, byteOffset); + } - // Convert strB using stackalloc for <= 256 characters and ArrayPool otherwise + if (!Utf8Utility.AllBytesInUInt32AreAscii(valueAu32 | valueBu32)) + { + // one of the inputs contains non-ASCII data + goto NonAscii32; + } - char[]? strBUtf16Array; - scoped Span strBUtf16; - int strBMaxCharCount = Encoding.UTF8.GetMaxCharCount(lengthB); + if (lengthA != lengthB) + { + // Failure if we reached the end of one, but not both sequences + return false; + } - if (strBMaxCharCount <= 256) + if (valueAu32 == valueBu32) + { + // exact match + return true; + } + + if (!Utf8Utility.UInt32OrdinalIgnoreCaseAscii(valueAu32, valueBu32)) + { + return false; + } + + byteOffset += 4; + length -= 4; + } + + Debug.Assert(length == 0); + return true; + + NonAscii32: + // Both values have to be non-ASCII to use the slow fallback, in case if one of them is not we return false + if (Utf8Utility.AllBytesInUInt32AreAscii(valueAu32) || Utf8Utility.AllBytesInUInt32AreAscii(valueBu32)) { - strBUtf16Array = null; - strBUtf16 = stackalloc char[512]; + return false; } - else + goto NonAscii; + +#if TARGET_64BIT + NonAscii64: + // Both values have to be non-ASCII to use the slow fallback, in case if one of them is not we return false + if (Utf8Utility.AllBytesInUInt64AreAscii(valueAu64) || Utf8Utility.AllBytesInUInt64AreAscii(valueBu64)) { - strBUtf16Array = ArrayPool.Shared.Rent(strBMaxCharCount); - strBUtf16 = strBUtf16Array.AsSpan(0, strBMaxCharCount); + return false; } +#endif + NonAscii: + range -= length; - int strBUtf16Length = Encoding.UTF8.GetChars(MemoryMarshal.CreateReadOnlySpan(ref strB, lengthB), strBUtf16); - strBUtf16 = strBUtf16.Slice(0, strBUtf16Length); + // The non-ASCII case is factored out into its own helper method so that the JIT + // doesn't need to emit a complex prolog for its caller (this method). + return EqualsStringIgnoreCaseUtf8(ref Unsafe.AddByteOffset(ref charA, byteOffset), lengthA - range, ref Unsafe.AddByteOffset(ref charB, byteOffset), lengthB - range); + } - // Actual operation + internal static bool StartsWithStringIgnoreCaseUtf8(ref byte source, int sourceLength, ref byte prefix, int prefixLength) + { + // NOTE: Two UTF-8 inputs of different length might compare as equal under + // the OrdinalIgnoreCase comparer. This is distinct from UTF-16, where the + // inputs being different length will mean that they can never compare as + // equal under an OrdinalIgnoreCase comparer. - int result = CompareStringIgnoreCaseNonAscii( - ref MemoryMarshal.GetReference(strAUtf16), strAUtf16.Length, - ref MemoryMarshal.GetReference(strBUtf16), strBUtf16.Length - ); + int length = Math.Min(sourceLength, prefixLength); + int range = length; + + const byte maxChar = 0x7F; - // Return rented buffers if necessary + while ((length != 0) && (source <= maxChar) && (prefix <= maxChar)) + { + // Ordinal equals or lowercase equals if the result ends up in the a-z range + if (source == prefix || + ((source | 0x20) == (prefix | 0x20) && char.IsAsciiLetter((char)source))) + { + length--; + source = ref Unsafe.Add(ref source, 1); + prefix = ref Unsafe.Add(ref prefix, 1); + } + else + { + return false; + } + } - if (strBUtf16Array != null) + if (length == 0) { - ArrayPool.Shared.Return(strBUtf16Array); + // Success if we reached the end of the prefix + return prefixLength == 0; } - if (strAUtf16Array != null) + range -= length; + return StartsWithStringIgnoreCaseNonAsciiUtf8(ref source, sourceLength - range, ref prefix, prefixLength - range); + } + + internal static bool StartsWithStringIgnoreCaseNonAsciiUtf8(ref byte source, int sourceLength, ref byte prefix, int prefixLength) + { + // NLS/ICU doesn't provide native UTF-8 support so we need to do our own corresponding ordinal comparison + + ReadOnlySpan spanA = MemoryMarshal.CreateReadOnlySpan(ref source, sourceLength); + ReadOnlySpan spanB = MemoryMarshal.CreateReadOnlySpan(ref prefix, prefixLength); + + do { - ArrayPool.Shared.Return(strAUtf16Array); + OperationStatus statusA = Rune.DecodeFromUtf8(spanA, out Rune runeA, out int bytesConsumedA); + OperationStatus statusB = Rune.DecodeFromUtf8(spanB, out Rune runeB, out int bytesConsumedB); + + if (statusA != statusB) + { + // OperationStatus don't match; fail immediately + return false; + } + + if (statusA == OperationStatus.Done) + { + if (Rune.ToUpperInvariant(runeA) != Rune.ToUpperInvariant(runeB)) + { + // Runes don't match when ignoring case; fail immediately + return false; + } + } + else if (!spanA.Slice(0, bytesConsumedA).SequenceEqual(spanB.Slice(0, bytesConsumedB))) + { + // OperationStatus match, but bytesConsumed or the sequence of bytes consumed do not; fail immediately + return false; + } + + // The current runes or invalid byte sequences matched, slice and continue. + // We'll exit the loop when the entirety of spanB has been processed. + // + // In the scenario where spanB is empty before spanB, we'll end up with that + // span returning OperationStatus.NeedsMoreData and bytesConsumed=0 while spanB + // will return a different OperationStatus or different bytesConsumed and thus + // fail the operation. + + spanA = spanA.Slice(bytesConsumedA); + spanB = spanB.Slice(bytesConsumedB); } + while (spanB.Length != 0); - return result; + return true; } - private static bool EqualsIgnoreCaseUtf8_Vector128(ref byte charA, ref byte charB, int length) + private static bool StartsWithIgnoreCaseUtf8_Vector128(ref byte source, int sourceLength, ref byte prefix, int prefixLength) { - Debug.Assert(length >= Vector128.Count); + Debug.Assert(sourceLength >= Vector128.Count); + Debug.Assert(prefixLength >= Vector128.Count); Debug.Assert(Vector128.IsHardwareAccelerated); - nuint lengthU = (nuint)length; + nuint lengthU = Math.Min((uint)sourceLength, (uint)prefixLength); nuint lengthToExamine = lengthU - (nuint)Vector128.Count; + nuint i = 0; + Vector128 vec1; Vector128 vec2; + do { - vec1 = Vector128.LoadUnsafe(ref charA, i); - vec2 = Vector128.LoadUnsafe(ref charB, i); + vec1 = Vector128.LoadUnsafe(ref source, i); + vec2 = Vector128.LoadUnsafe(ref prefix, i); if (!Utf8Utility.AllBytesInVector128AreAscii(vec1 | vec2)) { @@ -155,10 +448,17 @@ private static bool EqualsIgnoreCaseUtf8_Vector128(ref byte charA, ref byte char } i += (nuint)Vector128.Count; - } while (i <= lengthToExamine); + } + while (i <= lengthToExamine); + + if (i == (uint)prefixLength) + { + // success if we reached the end of the prefix + return true; + } // Use scalar path for trailing elements - return i == lengthU || EqualsIgnoreCaseUtf8(ref Unsafe.Add(ref charA, i), ref Unsafe.Add(ref charB, i), (int)(lengthU - i)); + return StartsWithIgnoreCaseUtf8_Scalar(ref Unsafe.Add(ref source, i), (int)(lengthU - i), ref Unsafe.Add(ref prefix, i), (int)(lengthU - i)); NON_ASCII: if (Utf8Utility.AllBytesInVector128AreAscii(vec1) || Utf8Utility.AllBytesInVector128AreAscii(vec2)) @@ -168,41 +468,47 @@ private static bool EqualsIgnoreCaseUtf8_Vector128(ref byte charA, ref byte char } // Fallback for Non-ASCII inputs - return CompareStringIgnoreCaseUtf8( - ref Unsafe.Add(ref charA, i), (int)(lengthU - i), - ref Unsafe.Add(ref charB, i), (int)(lengthU - i) - ) == 0; + return StartsWithStringIgnoreCaseUtf8( + ref Unsafe.Add(ref source, i), sourceLength - (int)i, + ref Unsafe.Add(ref prefix, i), prefixLength - (int)i + ); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool EqualsIgnoreCaseUtf8(ref byte charA, ref byte charB, int length) + internal static bool StartsWithIgnoreCaseUtf8(ref byte source, int sourceLength, ref byte prefix, int prefixLength) { - if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) + if (!Vector128.IsHardwareAccelerated || (sourceLength < Vector128.Count) || (prefixLength < Vector128.Count)) { - return EqualsIgnoreCaseUtf8_Scalar(ref charA, ref charB, length); + return StartsWithIgnoreCaseUtf8_Scalar(ref source, sourceLength, ref prefix, prefixLength); } - return EqualsIgnoreCaseUtf8_Vector128(ref charA, ref charB, length); + return StartsWithIgnoreCaseUtf8_Vector128(ref source, sourceLength, ref prefix, prefixLength); } - internal static bool EqualsIgnoreCaseUtf8_Scalar(ref byte charA, ref byte charB, int length) + internal static bool StartsWithIgnoreCaseUtf8_Scalar(ref byte source, int sourceLength, ref byte prefix, int prefixLength) { IntPtr byteOffset = IntPtr.Zero; #if TARGET_64BIT + int length = Math.Min(sourceLength, prefixLength); + int range = length; + ulong valueAu64 = 0; ulong valueBu64 = 0; + // Read 8 chars (64 bits) at a time from each string while ((uint)length >= 8) { - valueAu64 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charA, byteOffset)); - valueBu64 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charB, byteOffset)); + valueAu64 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref source, byteOffset)); + valueBu64 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref prefix, byteOffset)); // A 32-bit test - even with the bit-twiddling here - is more efficient than a 64-bit test. ulong temp = valueAu64 | valueBu64; + if (!Utf8Utility.AllBytesInUInt32AreAscii((uint)temp | (uint)(temp >> 32))) { - goto NonAscii64; // one of the inputs contains non-ASCII data + // one of the inputs contains non-ASCII data + goto NonAscii64; } // Generally, the caller has likely performed a first-pass check that the input strings @@ -221,8 +527,10 @@ internal static bool EqualsIgnoreCaseUtf8_Scalar(ref byte charA, ref byte charB, length -= 8; } #endif + uint valueAu32 = 0; uint valueBu32 = 0; + // Read 4 chars (32 bits) at a time from each string #if TARGET_64BIT if ((uint)length >= 4) @@ -230,12 +538,13 @@ internal static bool EqualsIgnoreCaseUtf8_Scalar(ref byte charA, ref byte charB, while ((uint)length >= 4) #endif { - valueAu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charA, byteOffset)); - valueBu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charB, byteOffset)); + valueAu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref source, byteOffset)); + valueBu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref prefix, byteOffset)); if (!Utf8Utility.AllBytesInUInt32AreAscii(valueAu32 | valueBu32)) { - goto NonAscii32; // one of the inputs contains non-ASCII data + // one of the inputs contains non-ASCII data + goto NonAscii32; } // Generally, the caller has likely performed a first-pass check that the input strings @@ -264,25 +573,25 @@ internal static bool EqualsIgnoreCaseUtf8_Scalar(ref byte charA, ref byte charB, if (length == 3) { - valueAu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charA, byteOffset)); - valueBu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charB, byteOffset)); + valueAu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref source, byteOffset)); + valueBu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref prefix, byteOffset)); byteOffset += 2; - valueAu32 |= (uint)(Unsafe.AddByteOffset(ref charA, byteOffset) << 16); - valueBu32 |= (uint)(Unsafe.AddByteOffset(ref charB, byteOffset) << 16); + valueAu32 |= (uint)(Unsafe.AddByteOffset(ref source, byteOffset) << 16); + valueBu32 |= (uint)(Unsafe.AddByteOffset(ref prefix, byteOffset) << 16); } else if (length == 2) { - valueAu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charA, byteOffset)); - valueBu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref charB, byteOffset)); + valueAu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref source, byteOffset)); + valueBu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref prefix, byteOffset)); } else { Debug.Assert(length == 1); - valueAu32 = Unsafe.AddByteOffset(ref charA, byteOffset); - valueBu32 = Unsafe.AddByteOffset(ref charB, byteOffset); + valueAu32 = Unsafe.AddByteOffset(ref source, byteOffset); + valueBu32 = Unsafe.AddByteOffset(ref prefix, byteOffset); } if (!Utf8Utility.AllBytesInUInt32AreAscii(valueAu32 | valueBu32)) @@ -290,6 +599,12 @@ internal static bool EqualsIgnoreCaseUtf8_Scalar(ref byte charA, ref byte charB, goto NonAscii32; // one of the inputs contains non-ASCII data } + if (range != prefixLength) + { + // Failure if we didn't reach the end of the prefix + return false; + } + if (valueAu32 == valueBu32) { return true; // exact match @@ -324,9 +639,11 @@ internal static bool EqualsIgnoreCaseUtf8_Scalar(ref byte charA, ref byte charB, } #endif NonAscii: + range -= length; + // The non-ASCII case is factored out into its own helper method so that the JIT // doesn't need to emit a complex prolog for its caller (this method). - return CompareStringIgnoreCaseUtf8(ref Unsafe.AddByteOffset(ref charA, byteOffset), length, ref Unsafe.AddByteOffset(ref charB, byteOffset), length) == 0; + return StartsWithStringIgnoreCaseUtf8(ref Unsafe.AddByteOffset(ref source, byteOffset), sourceLength - range, ref Unsafe.AddByteOffset(ref prefix, byteOffset), prefixLength - range); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.Utf8.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.Utf8.cs index 30c61d99a3135..c9690c96954ba 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.Utf8.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.Utf8.cs @@ -13,17 +13,15 @@ public static partial class MemoryExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool EqualsOrdinalIgnoreCaseUtf8(this ReadOnlySpan span, ReadOnlySpan value) { - if (span.Length != value.Length) - { - return false; - } + // For UTF-8 ist is possible for two spans of different byte length + // to compare as equal under an OrdinalIgnoreCase comparison. - if (value.Length == 0) // span.Length == value.Length == 0 + if ((span.Length | value.Length) == 0) // span.Length == value.Length == 0 { return true; } - return Ordinal.EqualsIgnoreCaseUtf8(ref MemoryMarshal.GetReference(span), ref MemoryMarshal.GetReference(value), span.Length); + return Ordinal.EqualsIgnoreCaseUtf8(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(value), value.Length); } /// @@ -66,8 +64,15 @@ internal static bool StartsWithUtf8(this ReadOnlySpan span, ReadOnlySpan span, ReadOnlySpan value) { - return (value.Length <= span.Length) - && Ordinal.EqualsIgnoreCaseUtf8(ref MemoryMarshal.GetReference(span), ref MemoryMarshal.GetReference(value), value.Length); + // For UTF-8 ist is possible for two spans of different byte length + // to compare as equal under an OrdinalIgnoreCase comparison. + + if ((span.Length | value.Length) == 0) // span.Length == value.Length == 0 + { + return true; + } + + return Ordinal.StartsWithIgnoreCaseUtf8(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(value), value.Length); } } } From c7676cd55e27fa9af234893fd0f997fede5d0cbc Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 6 Jul 2023 10:59:07 -0700 Subject: [PATCH 14/19] Fix a slightly broken merge --- .../src/System/Globalization/Ordinal.Utf8.cs | 2 +- .../System.Private.CoreLib/src/System/Number.Parsing.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs index 028547e6a8e5f..f1dd67a07f55e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs @@ -173,10 +173,10 @@ internal static bool EqualsIgnoreCaseUtf8_Scalar(ref byte charA, int lengthA, re { IntPtr byteOffset = IntPtr.Zero; -#if TARGET_64BIT int length = Math.Min(lengthA, lengthB); int range = length; +#if TARGET_64BIT ulong valueAu64 = 0; ulong valueBu64 = 0; 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 848969abbf37c..7716424359425 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -1304,7 +1304,7 @@ private static bool TrailingZeros(ReadOnlySpan value, int index) where TChar : unmanaged, IUtfChar { // For compatibility, we need to allow trailing zeros at the end of a number string - return !value.Slice(index).ContainsAnyExcept('\0'); + return !value.Slice(index).ContainsAnyExcept(TChar.CastFrom('\0')); } private static bool IsSpaceReplacingChar(uint c) => (c == '\u00a0') || (c == '\u202f'); From 55544369ac23fab8672900f22ff3c987423b1df5 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 6 Jul 2023 13:47:22 -0700 Subject: [PATCH 15/19] Ensure the lengths are properly checked on the ASCII path --- .../src/System/Globalization/Ordinal.Utf8.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs index f1dd67a07f55e..aebe39d4e2b04 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs @@ -306,7 +306,7 @@ internal static bool EqualsIgnoreCaseUtf8_Scalar(ref byte charA, int lengthA, re } Debug.Assert(length == 0); - return true; + return lengthA == lengthB; NonAscii32: // Both values have to be non-ASCII to use the slow fallback, in case if one of them is not we return false @@ -620,7 +620,7 @@ internal static bool StartsWithIgnoreCaseUtf8_Scalar(ref byte source, int source } Debug.Assert(length == 0); - return true; + return prefixLength <= sourceLength; NonAscii32: // Both values have to be non-ASCII to use the slow fallback, in case if one of them is not we return false From c5ef2b2502ad5c7742fb7be3b1860907db774142 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 6 Jul 2023 22:22:05 -0700 Subject: [PATCH 16/19] Ensure that length is defined for 32-bit --- .../src/System/Globalization/Ordinal.Utf8.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs index aebe39d4e2b04..e1e708705bb13 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs @@ -489,10 +489,10 @@ internal static bool StartsWithIgnoreCaseUtf8_Scalar(ref byte source, int source { IntPtr byteOffset = IntPtr.Zero; -#if TARGET_64BIT int length = Math.Min(sourceLength, prefixLength); int range = length; +#if TARGET_64BIT ulong valueAu64 = 0; ulong valueBu64 = 0; From ab271db699a9f7164cf8fa0d6283f62da2c35082 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Tue, 11 Jul 2023 10:29:58 -0700 Subject: [PATCH 17/19] Responding to PR feedback and allow invalid sequences to self match --- .../System/Globalization/CompareInfo.Utf8.cs | 8 +- .../src/System/Numerics/INumberBase.cs | 33 +++++- .../src/System/Text/Unicode/Utf8.cs | 107 ++++++++++++++++++ 3 files changed, 138 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Utf8.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Utf8.cs index f72147a48c34e..873aaca0094ec 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Utf8.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Utf8.cs @@ -85,7 +85,7 @@ private unsafe bool StartsWithCoreUtf8(ReadOnlySpan source, ReadOnlySpan source, ReadOnlySpan source, ReadOnlySpan source, ReadOnlySpan utf8Text, NumberStyles style, IFor utf16Text = utf16TextArray.AsSpan(0, textMaxCharCount); } - int utf16TextLength = Encoding.UTF8.GetChars(utf8Text, utf16Text); + OperationStatus utf8TextStatus = Utf8.ToUtf16(utf8Text, utf16Text, out _, out int utf16TextLength, replaceInvalidSequences: false); + + if (sourceStatus != OperationStatus.Done) + { + return ThrowHelper.ThrowFormatInvalidString(); + } utf16Text = utf16Text.Slice(0, utf16TextLength); // Actual operation @@ -427,7 +433,12 @@ static virtual bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IF utf16Text = utf16TextArray.AsSpan(0, textMaxCharCount); } - int utf16TextLength = Encoding.UTF8.GetChars(utf8Text, utf16Text); + OperationStatus utf8TextStatus = Utf8.ToUtf16(utf8Text, utf16Text, out _, out int utf16TextLength, replaceInvalidSequences: false); + + if (sourceStatus != OperationStatus.Done) + { + return false; + } utf16Text = utf16Text.Slice(0, utf16TextLength); // Actual operation @@ -455,7 +466,7 @@ static TSelf IUtf8SpanParsable.Parse(ReadOnlySpan utf8Text, IFormat if (textMaxCharCount < 256) { utf16TextArray = null; - utf16Text = stackalloc char[512]; + utf16Text = stackalloc char[256]; } else { @@ -463,7 +474,12 @@ static TSelf IUtf8SpanParsable.Parse(ReadOnlySpan utf8Text, IFormat utf16Text = utf16TextArray.AsSpan(0, textMaxCharCount); } - int utf16TextLength = Encoding.UTF8.GetChars(utf8Text, utf16Text); + OperationStatus utf8TextStatus = Utf8.ToUtf16(utf8Text, utf16Text, out _, out int utf16TextLength, replaceInvalidSequences: false); + + if (sourceStatus != OperationStatus.Done) + { + return ThrowHelper.ThrowFormatInvalidString(); + } utf16Text = utf16Text.Slice(0, utf16TextLength); // Actual operation @@ -491,7 +507,7 @@ static bool IUtf8SpanParsable.TryParse(ReadOnlySpan utf8Text, IForm if (textMaxCharCount < 256) { utf16TextArray = null; - utf16Text = stackalloc char[512]; + utf16Text = stackalloc char[256]; } else { @@ -499,7 +515,12 @@ static bool IUtf8SpanParsable.TryParse(ReadOnlySpan utf8Text, IForm utf16Text = utf16TextArray.AsSpan(0, textMaxCharCount); } - int utf16TextLength = Encoding.UTF8.GetChars(utf8Text, utf16Text); + OperationStatus utf8TextStatus = Utf8.ToUtf16(utf8Text, utf16Text, out _, out int utf16TextLength, replaceInvalidSequences: false); + + if (sourceStatus != OperationStatus.Done) + { + return false; + } utf16Text = utf16Text.Slice(0, utf16TextLength); // Actual operation diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/Unicode/Utf8.cs b/src/libraries/System.Private.CoreLib/src/System/Text/Unicode/Utf8.cs index a9c247630504c..b7e4fe5ce6386 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/Unicode/Utf8.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/Unicode/Utf8.cs @@ -139,6 +139,9 @@ public static unsafe OperationStatus ToUtf16(ReadOnlySpan source, Span source, Span source, Span destination, out int bytesRead, out int charsWritten, bool replaceInvalidSequences = true, bool isFinalBlock = true) + { + // Throwaway span accesses - workaround for https://github.com/dotnet/runtime/issues/12332 + + // NOTE: Changes to this method should be kept in sync with ToUtf16 above. + // + // This method exists to allow certain internal comparisons to function as expected under ICU. + // Essentially, ICU treats invalid UTF-16 sequences as opaque characters that only compare + // equal to themselves. This means "\uD800\uD801".StartsWith("\uD800") returns true. To support + // similar for UTF-8 and allow comparisons like "\xFF\xFE"u8.CultureAwareStartsWith("\xFF"u8) + // to also return true, we replace each character in an invalid UTF-8 sequence such that it + // becomes 0xDF?? where ?? is the individual UTF-8 byte. Thus the above becomes 0xDFFF, 0xDFFE. + // This allows them to compare as invalid UTF-16 sequences and thus only match with the same + // invalid sequence. + + _ = source.Length; + _ = destination.Length; + + // We'll be mutating these values throughout our loop. + + fixed (byte* pOriginalSource = &MemoryMarshal.GetReference(source)) + fixed (char* pOriginalDestination = &MemoryMarshal.GetReference(destination)) + { + // We're going to bulk transcode as much as we can in a loop, iterating + // every time we see bad data that requires replacement. + + OperationStatus operationStatus = OperationStatus.Done; + byte* pInputBufferRemaining = pOriginalSource; + char* pOutputBufferRemaining = pOriginalDestination; + + while (!source.IsEmpty) + { + // We've pinned the spans at the entry point to this method. + // It's safe for us to use Unsafe.AsPointer on them during this loop. + + operationStatus = Utf8Utility.TranscodeToUtf16( + pInputBuffer: (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(source)), + inputLength: source.Length, + pOutputBuffer: (char*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(destination)), + outputCharsRemaining: destination.Length, + pInputBufferRemaining: out pInputBufferRemaining, + pOutputBufferRemaining: out pOutputBufferRemaining); + + // If we finished the operation entirely or we ran out of space in the destination buffer, + // or if we need more input data and the caller told us that there's possibly more data + // coming, return immediately. + + if (operationStatus <= OperationStatus.DestinationTooSmall + || (operationStatus == OperationStatus.NeedMoreData && !isFinalBlock)) + { + break; + } + + // We encountered invalid data, or we need more data but the caller told us we're + // at the end of the stream. In either case treat this as truly invalid. + // If the caller didn't tell us to replace invalid sequences, return immediately. + + if (!replaceInvalidSequences) + { + operationStatus = OperationStatus.InvalidData; // status code may have been NeedMoreData - force to be error + break; + } + + // We're going to attempt to write U+DF?? to the destination buffer for each invalid byte + // + // Figure out how many bytes of the source we must skip over before we should retry + // the operation. This might be more than 1 byte. + // + // Check if we even have enough space to do so? + + source = source.Slice((int)(pInputBufferRemaining - (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(source)))); + destination = destination.Slice((int)(pOutputBufferRemaining - (char*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(destination)))); + + Debug.Assert(!source.IsEmpty, "Expected 'Done' if source is fully consumed."); + Rune.DecodeFromUtf8(source, out _, out int bytesConsumedJustNow); + + if (destination.Length < bytesConsumedJustNow) + { + operationStatus = OperationStatus.DestinationTooSmall; + break; + } + + for (int i = 0; i < bytesConsumedJustNow; i++) + { + destination[i] = (char)(0xDF00 | source[i]); + } + + destination = destination.Slice(bytesConsumedJustNow); + source = source.Slice(bytesConsumedJustNow); + + operationStatus = OperationStatus.Done; // we patched the error - if we're about to break out of the loop this is a success case + + pInputBufferRemaining = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(source)); + pOutputBufferRemaining = (char*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(destination)); + } + + // Not possible to make any further progress - report to our caller how far we got. + + bytesRead = (int)(pInputBufferRemaining - pOriginalSource); + charsWritten = (int)(pOutputBufferRemaining - pOriginalDestination); + return operationStatus; + } + } + /// Writes the specified interpolated string to the UTF8 byte span. /// The span to which the interpolated string should be formatted. /// The interpolated string. From a78b75bfa661100ea5861e2c25709027a627ed59 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Tue, 11 Jul 2023 14:31:44 -0700 Subject: [PATCH 18/19] Fixing a copy/paste error --- .../src/System/Numerics/INumberBase.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 bf987542ea443..5b02371f95922 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs @@ -307,7 +307,7 @@ static virtual TSelf Parse(ReadOnlySpan utf8Text, NumberStyles style, IFor OperationStatus utf8TextStatus = Utf8.ToUtf16(utf8Text, utf16Text, out _, out int utf16TextLength, replaceInvalidSequences: false); - if (sourceStatus != OperationStatus.Done) + if (utf8TextStatus != OperationStatus.Done) { return ThrowHelper.ThrowFormatInvalidString(); } @@ -435,7 +435,7 @@ static virtual bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IF OperationStatus utf8TextStatus = Utf8.ToUtf16(utf8Text, utf16Text, out _, out int utf16TextLength, replaceInvalidSequences: false); - if (sourceStatus != OperationStatus.Done) + if (utf8TextStatus != OperationStatus.Done) { return false; } @@ -476,7 +476,7 @@ static TSelf IUtf8SpanParsable.Parse(ReadOnlySpan utf8Text, IFormat OperationStatus utf8TextStatus = Utf8.ToUtf16(utf8Text, utf16Text, out _, out int utf16TextLength, replaceInvalidSequences: false); - if (sourceStatus != OperationStatus.Done) + if (utf8TextStatus != OperationStatus.Done) { return ThrowHelper.ThrowFormatInvalidString(); } @@ -517,7 +517,7 @@ static bool IUtf8SpanParsable.TryParse(ReadOnlySpan utf8Text, IForm OperationStatus utf8TextStatus = Utf8.ToUtf16(utf8Text, utf16Text, out _, out int utf16TextLength, replaceInvalidSequences: false); - if (sourceStatus != OperationStatus.Done) + if (utf8TextStatus != OperationStatus.Done) { return false; } From b535e5cfbef18abf26cc165d80bc09599ec9120c Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Tue, 11 Jul 2023 16:04:56 -0700 Subject: [PATCH 19/19] Fixing a build failure due to not assigning the out parameter --- .../src/System/Numerics/INumberBase.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 5b02371f95922..4d943b1933888 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs @@ -309,7 +309,7 @@ static virtual TSelf Parse(ReadOnlySpan utf8Text, NumberStyles style, IFor if (utf8TextStatus != OperationStatus.Done) { - return ThrowHelper.ThrowFormatInvalidString(); + ThrowHelper.ThrowFormatInvalidString(); } utf16Text = utf16Text.Slice(0, utf16TextLength); @@ -437,6 +437,7 @@ static virtual bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IF if (utf8TextStatus != OperationStatus.Done) { + result = default; return false; } utf16Text = utf16Text.Slice(0, utf16TextLength); @@ -478,7 +479,7 @@ static TSelf IUtf8SpanParsable.Parse(ReadOnlySpan utf8Text, IFormat if (utf8TextStatus != OperationStatus.Done) { - return ThrowHelper.ThrowFormatInvalidString(); + ThrowHelper.ThrowFormatInvalidString(); } utf16Text = utf16Text.Slice(0, utf16TextLength); @@ -519,6 +520,7 @@ static bool IUtf8SpanParsable.TryParse(ReadOnlySpan utf8Text, IForm if (utf8TextStatus != OperationStatus.Done) { + result = default; return false; } utf16Text = utf16Text.Slice(0, utf16TextLength);