From 1dd0fa2b32bda8b438963c6465f0b39d0714632a Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Tue, 11 Jul 2023 22:12:34 -0700 Subject: [PATCH] Expose IUtf8SpanParsable and implement it on the primitive numeric types (#86875) * Expose IUtf8SpanParsable * Have INumberBase implement IUtf8SpanParsable * Deduplicate some floating-point parsing logic * Refactoring the primitive parsing logic to support UTF-8 * Updating the primitive numeric types to include UTF-8 parsing support * Adding tests covering the new UTF-8 parsing support * Ensure that tests don't try to capture a span in lambda * Ensure that MatchChars does the right thing * Account for the switch from string to ROSpan for currSymbol * Ensure EqualsIgnoreCaseUtf8_Scalar handles the remaining elements correctly * Allow alloc-free conversion for the UTF8 parsing fallback paths * Remove unnecessary attributes and ensure trailing elements are correctly handled * Fix the handling of UTF8 as per the feedback * Fix a slightly broken merge * Ensure the lengths are properly checked on the ASCII path * Ensure that length is defined for 32-bit * Responding to PR feedback and allow invalid sequences to self match * Fixing a copy/paste error * Fixing a build failure due to not assigning the out parameter --- .../System.Private.CoreLib.Shared.projitems | 5 + .../Text/Utf8Parser/Utf8Parser.Float.cs | 4 +- .../System.Private.CoreLib/src/System/Byte.cs | 36 +- .../System.Private.CoreLib/src/System/Char.cs | 2 + .../src/System/Decimal.DecCalc.cs | 16 +- .../src/System/Decimal.cs | 90 +- .../src/System/Double.cs | 126 ++- .../System.Private.CoreLib/src/System/Enum.cs | 4 +- .../System/Globalization/CompareInfo.Utf8.cs | 148 ++++ .../src/System/Globalization/Ordinal.Utf8.cs | 649 ++++++++++++++ .../src/System/Globalization/Ordinal.cs | 2 +- .../System.Private.CoreLib/src/System/Half.cs | 127 ++- .../src/System/IUtf8SpanParsable.cs | 28 + .../src/System/IUtfChar.cs | 3 + .../src/System/Int128.cs | 34 +- .../src/System/Int16.cs | 34 +- .../src/System/Int32.cs | 34 +- .../src/System/Int64.cs | 34 +- .../src/System/IntPtr.cs | 34 + .../MemoryExtensions.Globalization.Utf8.cs | 78 ++ .../src/System/MemoryExtensions.Trim.Utf8.cs | 76 ++ .../src/System/Number.Formatting.cs | 6 +- .../Number.NumberToFloatingPointBits.cs | 377 ++------ .../src/System/Number.Parsing.cs | 833 +++++++++--------- .../src/System/Numerics/INumberBase.cs | 187 +++- .../src/System/ParseNumbers.cs | 24 +- .../System/Runtime/InteropServices/NFloat.cs | 36 +- .../src/System/SByte.cs | 34 +- .../src/System/Single.cs | 129 ++- .../src/System/Text/Unicode/Utf8.cs | 107 +++ .../src/System/Text/Unicode/Utf8Utility.cs | 126 +++ .../src/System/UInt128.cs | 36 +- .../src/System/UInt16.cs | 34 +- .../src/System/UInt32.cs | 34 +- .../src/System/UInt64.cs | 34 +- .../src/System/UIntPtr.cs | 34 + .../ref/System.Runtime.InteropServices.cs | 5 + .../System.Runtime/ref/System.Runtime.cs | 91 +- .../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 + 54 files changed, 3446 insertions(+), 948 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/IUtf8SpanParsable.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 439ee87e6868f..005c30cb021db 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 @@ -330,6 +330,7 @@ + @@ -375,6 +376,7 @@ + @@ -510,6 +512,7 @@ + @@ -526,7 +529,9 @@ + + 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/Byte.cs b/src/libraries/System.Private.CoreLib/src/System/Byte.cs index e778864eedb92..31a2bccf21bf8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Byte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Byte.cs @@ -108,13 +108,19 @@ 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); 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); @@ -124,7 +130,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) @@ -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 // @@ -1161,6 +1191,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 b7e2488712cfc..5b75e639ae6f5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Char.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Char.cs @@ -1993,6 +1993,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 b6c6c0704ace1..e5c40133c690f 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) @@ -732,10 +714,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 +735,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 +755,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 +830,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; } @@ -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 48067b789891b..ba621512477f4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Double.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Double.cs @@ -26,7 +26,8 @@ public readonly struct Double IEquatable, IBinaryFloatingPointIeee754, IMinMaxValue, - IUtf8SpanFormattable + IUtf8SpanFormattable, + IBinaryFloatParseAndFormatInfo { private readonly double m_value; // Do not rename (binary serialization) @@ -365,30 +366,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.ParseDouble(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.ParseDouble(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.ParseDouble(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.ParseDouble(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 @@ -402,24 +392,18 @@ 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) - { - if (s == null) - { - result = 0; - return false; - } + public static bool TryParse([NotNullWhen(true)] string? s, out double result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null, out result); - return TryParse((ReadOnlySpan)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(ReadOnlySpan s, out double result) - { - return TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo, 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) { @@ -430,19 +414,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.TryParseDouble(s, style, info, out result); + return Number.TryParseFloat(s, style, NumberFormatInfo.GetInstance(provider), out result); } // @@ -2207,6 +2185,70 @@ 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 + // + + 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/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..873aaca0094ec --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Utf8.cs @@ -0,0 +1,148 @@ +// 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; +using System.Text.Unicode; + +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 + + // Convert source using stackalloc for <= 256 characters and ArrayPool otherwise + + char[]? sourceUtf16Array; + scoped Span sourceUtf16; + int sourceMaxCharCount = Encoding.UTF8.GetMaxCharCount(source.Length); + + if (sourceMaxCharCount <= 256) + { + sourceUtf16Array = null; + sourceUtf16 = stackalloc char[256]; + } + else + { + sourceUtf16Array = ArrayPool.Shared.Rent(sourceMaxCharCount); + sourceUtf16 = sourceUtf16Array.AsSpan(0, sourceMaxCharCount); + } + + OperationStatus sourceStatus = Utf8.ToUtf16PreservingReplacement(source, sourceUtf16, out _, out int sourceUtf16Length, replaceInvalidSequences: true); + + if (sourceStatus != OperationStatus.Done) + { + return false; + } + 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[256]; + } + else + { + prefixUtf16Array = ArrayPool.Shared.Rent(prefixMaxCharCount); + prefixUtf16 = prefixUtf16Array.AsSpan(0, prefixMaxCharCount); + } + + OperationStatus prefixStatus = Utf8.ToUtf16PreservingReplacement(prefix, prefixUtf16, out _, out int prefixUtf16Length, replaceInvalidSequences: true); + + if (prefixStatus != OperationStatus.Done) + { + return false; + } + 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 new file mode 100644 index 0000000000000..e1e708705bb13 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.Utf8.cs @@ -0,0 +1,649 @@ +// 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; +using System.Runtime.Intrinsics; +using System.Text; +using System.Text.Unicode; + +namespace System.Globalization +{ + internal static partial class Ordinal + { + internal static bool EqualsStringIgnoreCaseUtf8(ref byte strA, int lengthA, ref byte strB, int lengthB) + { + // 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 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 + { + return false; + } + } + + if (length == 0) + { + // Success if we reached the end of both sequences + return lengthA == lengthB; + } + + range -= length; + return EqualsStringIgnoreCaseNonAsciiUtf8(ref charA, lengthA - range, ref charB, lengthB - range); + } + + internal static bool EqualsStringIgnoreCaseNonAsciiUtf8(ref byte strA, int lengthA, ref byte strB, int lengthB) + { + // NLS/ICU doesn't provide native UTF-8 support so we need to do our own corresponding ordinal comparison + + 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)) + { + // 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. + + 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 (i == lengthU) + { + // success if we reached the end of both sequences + return lengthA == lengthB; + } + + // 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 + ); + } + + [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 bool EqualsIgnoreCaseUtf8_Scalar(ref byte charA, int lengthA, ref byte charB, int lengthB) + { + IntPtr byteOffset = IntPtr.Zero; + + int length = Math.Min(lengthA, lengthB); + int range = length; + +#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))) + { + // 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; + } +#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 + { + 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; + } + + 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); + } + + if (!Utf8Utility.AllBytesInUInt32AreAscii(valueAu32 | valueBu32)) + { + // one of the inputs contains non-ASCII data + goto NonAscii32; + } + + if (lengthA != lengthB) + { + // Failure if we reached the end of one, but not both sequences + return false; + } + + if (valueAu32 == valueBu32) + { + // exact match + return true; + } + + if (!Utf8Utility.UInt32OrdinalIgnoreCaseAscii(valueAu32, valueBu32)) + { + return false; + } + + byteOffset += 4; + length -= 4; + } + + Debug.Assert(length == 0); + 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 + 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: + 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 EqualsStringIgnoreCaseUtf8(ref Unsafe.AddByteOffset(ref charA, byteOffset), lengthA - range, ref Unsafe.AddByteOffset(ref charB, byteOffset), lengthB - range); + } + + 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 length = Math.Min(sourceLength, prefixLength); + int range = length; + + const byte maxChar = 0x7F; + + 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 (length == 0) + { + // Success if we reached the end of the prefix + return prefixLength == 0; + } + + 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 + { + 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 true; + } + + private static bool StartsWithIgnoreCaseUtf8_Vector128(ref byte source, int sourceLength, ref byte prefix, int prefixLength) + { + Debug.Assert(sourceLength >= Vector128.Count); + Debug.Assert(prefixLength >= Vector128.Count); + Debug.Assert(Vector128.IsHardwareAccelerated); + + 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 source, i); + vec2 = Vector128.LoadUnsafe(ref prefix, i); + + if (!Utf8Utility.AllBytesInVector128AreAscii(vec1 | vec2)) + { + goto NON_ASCII; + } + + if (!Utf8Utility.Vector128OrdinalIgnoreCaseAscii(vec1, vec2)) + { + return false; + } + + i += (nuint)Vector128.Count; + } + 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 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)) + { + // No need to use the fallback if one of the inputs is full-ASCII + return false; + } + + // Fallback for Non-ASCII inputs + 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 StartsWithIgnoreCaseUtf8(ref byte source, int sourceLength, ref byte prefix, int prefixLength) + { + if (!Vector128.IsHardwareAccelerated || (sourceLength < Vector128.Count) || (prefixLength < Vector128.Count)) + { + return StartsWithIgnoreCaseUtf8_Scalar(ref source, sourceLength, ref prefix, prefixLength); + } + + return StartsWithIgnoreCaseUtf8_Vector128(ref source, sourceLength, ref prefix, prefixLength); + } + + internal static bool StartsWithIgnoreCaseUtf8_Scalar(ref byte source, int sourceLength, ref byte prefix, int prefixLength) + { + IntPtr byteOffset = IntPtr.Zero; + + int length = Math.Min(sourceLength, prefixLength); + int range = length; + +#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 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))) + { + // 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; + } +#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 + { + valueAu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref source, byteOffset)); + valueBu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref prefix, 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; + } + + 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 source, byteOffset)); + valueBu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref prefix, byteOffset)); + + byteOffset += 2; + + 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 source, byteOffset)); + valueBu32 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref prefix, byteOffset)); + } + else + { + Debug.Assert(length == 1); + + valueAu32 = Unsafe.AddByteOffset(ref source, byteOffset); + valueBu32 = Unsafe.AddByteOffset(ref prefix, byteOffset); + } + + if (!Utf8Utility.AllBytesInUInt32AreAscii(valueAu32 | valueBu32)) + { + 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 + } + + if (!Utf8Utility.UInt32OrdinalIgnoreCaseAscii(valueAu32, valueBu32)) + { + return false; + } + + byteOffset += 4; + length -= 4; + } + + Debug.Assert(length == 0); + 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 + 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: + 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 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/Globalization/Ordinal.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.cs index 40f68fa3b9b38..36854cd07bab7 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 91bcd1687bb67..58a7b7865ef82 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -25,7 +25,8 @@ public readonly struct Half IEquatable, IBinaryFloatingPointIeee754, IMinMaxValue, - IUtf8SpanFormattable + IUtf8SpanFormattable, + IBinaryFloatParseAndFormatInfo { private const NumberStyles DefaultParseStyle = NumberStyles.Float | NumberStyles.AllowThousands; @@ -55,6 +56,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; @@ -283,11 +287,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.ParseHalf(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 . @@ -295,12 +295,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.ParseHalf(s, style, NumberFormatInfo.CurrentInfo); - } + public static Half Parse(string s, NumberStyles style) => Parse(s, style, provider: null); /// /// Parses a from a and . @@ -308,11 +303,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.ParseHalf(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 . @@ -323,9 +314,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.ParseHalf(s, style, NumberFormatInfo.GetInstance(provider)); + if (s is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); + } + return Parse(s.AsSpan(), style, provider); } /// @@ -338,7 +331,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)); } /// @@ -347,15 +340,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. @@ -363,10 +348,13 @@ 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 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 . @@ -382,11 +370,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); } /// @@ -400,7 +387,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) @@ -2198,5 +2185,69 @@ 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 + // + + 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/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.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 3374654470865..50dce177e1713 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int128.cs @@ -141,13 +141,19 @@ 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); 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); @@ -157,7 +163,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) @@ -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 7b4261e77882a..af144fb860c92 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int16.cs @@ -141,13 +141,19 @@ 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); 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); @@ -157,7 +163,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) @@ -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 7660fbaaae8b3..2f91c2c2a5970 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int32.cs @@ -151,13 +151,19 @@ 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); 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); @@ -167,7 +173,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) @@ -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 cf8bf7850790f..ca170d8c903ed 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int64.cs @@ -148,13 +148,19 @@ 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); 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); @@ -164,7 +170,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) @@ -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 3c6434b0d2ce9..3dd33c2c66b82 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/MemoryExtensions.Globalization.Utf8.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.Utf8.cs new file mode 100644 index 0000000000000..c9690c96954ba --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.Utf8.cs @@ -0,0 +1,78 @@ +// 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) + { + // 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.EqualsIgnoreCaseUtf8(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(value), value.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. + 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) + { + // 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); + } + } +} 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..03c7acfcb2b8e --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Trim.Utf8.cs @@ -0,0 +1,76 @@ +// 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 + { + 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.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 ff94db3bcf9c8..7716424359425 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. @@ -45,6 +46,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 +95,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 @@ -126,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); @@ -159,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; @@ -201,10 +237,10 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu state |= StateSign | StateParens; number.IsNegative = true; } - else if (currSymbol != null && (next = MatchChars(p, strEnd, currSymbol)) != null) + else if (!currSymbol.IsEmpty && (next = MatchChars(p, strEnd, currSymbol)) != null) { state |= StateCurrency; - currSymbol = null; + currSymbol = ReadOnlySpan.Empty; // We already found the currency symbol. There should not be more currency symbols. Set // currSymbol to NULL so that we won't search it again in the later code path. p = next - 1; @@ -214,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; @@ -232,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; @@ -275,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; } @@ -288,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)) @@ -314,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)); @@ -334,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'; } } @@ -347,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'; } } @@ -355,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; @@ -364,9 +400,9 @@ private static unsafe bool TryParseNumber(scoped ref char* str, char* strEnd, Nu { state &= ~StateParens; } - else if (currSymbol != null && (next = MatchChars(p, strEnd, currSymbol)) != null) + else if (!currSymbol.IsEmpty && (next = MatchChars(p, strEnd, currSymbol)) != null) { - currSymbol = null; + currSymbol = ReadOnlySpan.Empty; p = next - 1; } else @@ -374,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) { @@ -398,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) @@ -414,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; @@ -440,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)) @@ -457,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)); } @@ -474,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]); } } } @@ -528,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)) @@ -546,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'); } @@ -566,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; } @@ -612,6 +705,7 @@ internal static ParsingStatus TryParseBinaryIntegerStyle(ReadOnlySpan< { goto OverflowExit; } + DoneAtEnd: if (!TInteger.IsSigned) { @@ -622,6 +716,7 @@ internal static ParsingStatus TryParseBinaryIntegerStyle(ReadOnlySpan< result = isNegative ? -answer : answer; } ParsingStatus status = ParsingStatus.OK; + Exit: return status; @@ -629,6 +724,7 @@ internal static ParsingStatus TryParseBinaryIntegerStyle(ReadOnlySpan< result = TInteger.Zero; status = ParsingStatus.Failed; goto Exit; + OverflowExit: result = TInteger.Zero; status = ParsingStatus.Overflow; @@ -639,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); @@ -673,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); @@ -683,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)) @@ -708,9 +819,12 @@ private static ParsingStatus TryParseBinaryIntegerHexOrBinaryNumberStyle= (uint)value.Length) + { goto FalseExit; - num = value[index]; + } + num = TChar.CastToUInt32(value[index]); } while (IsWhite(num)); } @@ -726,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; } @@ -777,9 +914,11 @@ private static ParsingStatus TryParseBinaryIntegerHexOrBinaryNumberStyle= (uint)value.Length) + { goto DoneAtEndButPotentialOverflow; + } } if (!TrailingZeros(value, index)) + { goto FalseExit; - + } goto DoneAtEndButPotentialOverflow; } - internal static decimal ParseDecimal(ReadOnlySpan 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) { - ThrowOverflowOrFormatException(status, value, TypeCode.Decimal); + if (status == ParsingStatus.Failed) + { + ThrowFormatException(value); + } + ThrowOverflowException(SR.Overflow_Decimal); } return result; @@ -871,9 +1027,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) { @@ -904,7 +1060,7 @@ internal static unsafe bool TryNumberToDecimal(ref NumberBuffer number, ref deci while ((c != 0) && hasZeroTail) { - hasZeroTail &= (c == '0'); + hasZeroTail &= c == '0'; c = *++p; } @@ -944,37 +1100,19 @@ internal static unsafe bool TryNumberToDecimal(ref NumberBuffer number, ref deci return true; } - internal static double ParseDouble(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) - { - 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) + internal static TFloat ParseFloat(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) + where TChar : unmanaged, IUtfChar + where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo { - if (!TryParseHalf(value, styles, info, out Half result)) + 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) + where TChar : unmanaged, IUtfChar { NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, stackalloc byte[DecimalNumberBufferLength]); @@ -993,217 +1131,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 unsafe bool TryParseDouble(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out double result) + internal static bool SpanStartsWith(ReadOnlySpan span, TChar c) + where TChar : unmanaged, IUtfChar { - NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, stackalloc byte[DoubleNumberBufferLength]); + return !span.IsEmpty && (span[0] == c); + } - if (!TryStringToNumber(value, styles, ref number, info)) + internal static bool SpanStartsWith(ReadOnlySpan span, ReadOnlySpan value, StringComparison comparisonType) + where TChar : unmanaged, IUtfChar + { + if (typeof(TChar) == typeof(char)) { - 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. - - if (valueTrim.EqualsOrdinalIgnoreCase(info.PositiveInfinitySymbol)) - { - result = double.PositiveInfinity; - } - else if (valueTrim.EqualsOrdinalIgnoreCase(info.NegativeInfinitySymbol)) - { - result = double.NegativeInfinity; - } - else if (valueTrim.EqualsOrdinalIgnoreCase(info.NaNSymbol)) - { - result = double.NaN; - } - else if (valueTrim.StartsWith(info.PositiveSign, StringComparison.OrdinalIgnoreCase)) - { - valueTrim = valueTrim.Slice(info.PositiveSign.Length); - - if (valueTrim.EqualsOrdinalIgnoreCase(info.PositiveInfinitySymbol)) - { - result = double.PositiveInfinity; - } - else if (valueTrim.EqualsOrdinalIgnoreCase(info.NaNSymbol)) - { - result = double.NaN; - } - else - { - result = 0; - 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 - } + 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 { - result = NumberToDouble(ref number); - } + Debug.Assert(typeof(TChar) == typeof(byte)); - return true; + 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); + } } - internal static unsafe bool TryParseHalf(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out Half result) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ReadOnlySpan SpanTrim(ReadOnlySpan span) + where TChar : unmanaged, IUtfChar { - NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, stackalloc byte[HalfNumberBufferLength]); - - if (!TryStringToNumber(value, styles, ref number, info)) + if (typeof(TChar) == typeof(char)) { - 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`. + 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)); - 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); + 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); + } + } - 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 - } + [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 { - result = NumberToHalf(ref number); - } + Debug.Assert(typeof(TChar) == typeof(byte)); - return true; + 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 TryParseSingle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out float 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[SingleNumberBufferLength]); + 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. - // - // 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)) + ReadOnlySpan positiveInfinitySymbol = info.PositiveInfinitySymbolTChar(); + + if (SpanEqualsOrdinalIgnoreCase(valueTrim, positiveInfinitySymbol)) { - result = float.PositiveInfinity; + result = TFloat.PositiveInfinity; + return true; } - else if (valueTrim.EqualsOrdinalIgnoreCase(info.NegativeInfinitySymbol)) + + if (SpanEqualsOrdinalIgnoreCase(valueTrim, info.NegativeInfinitySymbolTChar())) { - result = float.NegativeInfinity; + result = TFloat.NegativeInfinity; + return true; } - else if (valueTrim.EqualsOrdinalIgnoreCase(info.NaNSymbol)) + + ReadOnlySpan nanSymbol = info.NaNSymbolTChar(); + + if (SpanEqualsOrdinalIgnoreCase(valueTrim, nanSymbol)) { - result = float.NaN; + 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 (!info.PositiveInfinitySymbol.StartsWith(info.PositiveSign, StringComparison.OrdinalIgnoreCase) && valueTrim.EqualsOrdinalIgnoreCase(info.PositiveInfinitySymbol)) + if (SpanEqualsOrdinalIgnoreCase(valueTrim, positiveInfinitySymbol)) { - result = float.PositiveInfinity; + result = TFloat.PositiveInfinity; + return true; } - else if (!info.NaNSymbol.StartsWith(info.PositiveSign, StringComparison.OrdinalIgnoreCase) && valueTrim.EqualsOrdinalIgnoreCase(info.NaNSymbol)) + else if (SpanEqualsOrdinalIgnoreCase(valueTrim, nanSymbol)) { - result = float.NaN; + result = TFloat.NaN; + return true; } - 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.Zero; + return false; } - else + + ReadOnlySpan negativeSign = info.NegativeSignTChar(); + + if (SpanStartsWith(valueTrim, negativeSign, StringComparison.OrdinalIgnoreCase)) { - result = 0; - 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 = NumberToSingle(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)))) { @@ -1217,17 +1300,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).ContainsAnyExcept('\0'); + return !value.Slice(index).ContainsAnyExcept(TChar.CastFrom('\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; } @@ -1235,28 +1323,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 = &MemoryMarshal.GetReference(value)) { - char* str = stringPointer; - if (*str != '\0') + TChar* str = stringPointer; + + if (TChar.CastToUInt32(*str) != '\0') { // We only hurt the failure case // This fix is for French or Kazakh cultures. Since a user cannot type 0xA0 or 0x202F as a // space character we use 0x20 space character instead to mean the same. while (true) { - char cp = p < pEnd ? *p : '\0'; - if (cp != *str && !(IsSpaceReplacingChar(*str) && cp == '\u0020')) + uint cp = (p < pEnd) ? TChar.CastToUInt32(*p) : '\0'; + uint val = TChar.CastToUInt32(*str); + + if ((cp != val) && !(IsSpaceReplacingChar(val) && (cp == '\u0020'))) { break; } + p++; str++; - if (*str == '\0') + + if (TChar.CastToUInt32(*str) == '\0') + { return p; + } } } } @@ -1264,9 +1361,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 { @@ -1276,149 +1373,55 @@ 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); - } - - private static OverflowException GetOverflowException(TypeCode type) - { - 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); - } - - 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 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; + throw new OverflowException(TInteger.OverflowMessage); } - internal static Half NumberToHalf(ref NumberBuffer number) + [DoesNotReturn] + internal static void ThrowOverflowException(string message) { - 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; + throw new OverflowException(message); } - 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/Numerics/INumberBase.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs index 0040a887d171b..4d943b1933888 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs @@ -1,9 +1,12 @@ // 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; +using System.Text; +using System.Text.Unicode; namespace System.Numerics { @@ -23,7 +26,8 @@ public interface INumberBase ISpanParsable, ISubtractionOperators, IUnaryPlusOperators, - IUnaryNegationOperators + IUnaryNegationOperators, + IUtf8SpanParsable where TSelf : INumberBase? { /// Gets the value 1 for the type. @@ -274,6 +278,55 @@ 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) + { + // 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); + } + + OperationStatus utf8TextStatus = Utf8.ToUtf16(utf8Text, utf16Text, out _, out int utf16TextLength, replaceInvalidSequences: false); + + if (utf8TextStatus != OperationStatus.Done) + { + ThrowHelper.ThrowFormatInvalidString(); + } + 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. /// The type of . /// The value which is used to create the instance of . @@ -353,5 +406,137 @@ 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) + { + // 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); + } + + OperationStatus utf8TextStatus = Utf8.ToUtf16(utf8Text, utf16Text, out _, out int utf16TextLength, replaceInvalidSequences: false); + + if (utf8TextStatus != OperationStatus.Done) + { + result = default; + return false; + } + 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) + { + // 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[256]; + } + else + { + utf16TextArray = ArrayPool.Shared.Rent(textMaxCharCount); + utf16Text = utf16TextArray.AsSpan(0, textMaxCharCount); + } + + OperationStatus utf8TextStatus = Utf8.ToUtf16(utf8Text, utf16Text, out _, out int utf16TextLength, replaceInvalidSequences: false); + + if (utf8TextStatus != OperationStatus.Done) + { + ThrowHelper.ThrowFormatInvalidString(); + } + 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) + { + // 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[256]; + } + else + { + utf16TextArray = ArrayPool.Shared.Rent(textMaxCharCount); + utf16Text = utf16TextArray.AsSpan(0, textMaxCharCount); + } + + OperationStatus utf8TextStatus = Utf8.ToUtf16(utf8Text, utf16Text, out _, out int utf16TextLength, replaceInvalidSequences: false); + + if (utf8TextStatus != OperationStatus.Done) + { + result = default; + return false; + } + 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; + } } } 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/Runtime/InteropServices/NFloat.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NFloat.cs index e5645feb21ffa..f61091f19e932 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 496eefa87b10c..fb4734d41b69f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SByte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SByte.cs @@ -144,13 +144,19 @@ 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); 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); @@ -160,7 +166,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) @@ -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 273576e36a4dd..a4fa4fa4101c9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Single.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Single.cs @@ -26,7 +26,8 @@ public readonly struct Single IEquatable, IBinaryFloatingPointIeee754, IMinMaxValue, - IUtf8SpanFormattable + IUtf8SpanFormattable, + IBinaryFloatParseAndFormatInfo { private readonly float m_value; // Do not rename (binary serialization) @@ -101,6 +102,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 @@ -366,53 +370,36 @@ 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.ParseSingle(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.ParseSingle(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.ParseSingle(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.ParseSingle(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.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) - { - if (s == null) - { - result = 0; - return false; - } + public static bool TryParse([NotNullWhen(true)] string? s, out float result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null, out result); - return TryParse((ReadOnlySpan)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(ReadOnlySpan s, out float result) - { - return TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo, 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) { @@ -423,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.TryParseSingle(s, style, info, out result); + return Number.TryParseFloat(s, style, NumberFormatInfo.GetInstance(provider), out result); } // @@ -2085,6 +2066,70 @@ 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 + // + + 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 // 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. 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 00e61cd638302..e07f16e0b67f8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt128.cs @@ -142,13 +142,19 @@ 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); 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); @@ -158,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 UInt128 result) @@ -217,7 +223,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); @@ -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 3d86e2fe8640f..fcc863a6310a8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt16.cs @@ -136,13 +136,19 @@ 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); 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); @@ -152,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 ushort result) @@ -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 6f2209d5f347a..8487630193ec5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt32.cs @@ -146,13 +146,19 @@ 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); 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); @@ -162,7 +168,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) @@ -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 bb09e77c185fa..dc12c5c006a82 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt64.cs @@ -145,13 +145,19 @@ 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); 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); @@ -161,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 ulong result) @@ -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 79566c793c0f8..42c58a5d0baa8 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 5ece7af10e1d5..1744a5f234b81 100644 --- a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs +++ b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs @@ -1455,6 +1455,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; } @@ -1516,6 +1518,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 2d1c801ed229c..08e6907f7e80e 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; } @@ -2975,6 +2990,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; } @@ -3029,6 +3046,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; } @@ -3284,6 +3304,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; } @@ -3330,6 +3352,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; } @@ -3375,6 +3400,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; } @@ -3464,6 +3491,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; } @@ -3509,6 +3539,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; } @@ -3598,6 +3630,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; } @@ -3643,6 +3678,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; } @@ -3732,6 +3769,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; } @@ -3795,6 +3835,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; } @@ -3873,6 +3915,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; } @@ -3946,6 +3991,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() { } @@ -4772,6 +4822,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; } @@ -4861,6 +4913,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; } @@ -4981,6 +5036,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; } @@ -5059,6 +5116,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 +6467,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 +6521,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 +6563,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 +6660,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 +6702,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 +6799,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 +6841,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 +6938,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 +6995,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 +7080,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; } @@ -10708,7 +10793,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; } @@ -10747,8 +10832,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; @@ -10773,6 +10861,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); } diff --git a/src/libraries/System.Runtime/tests/System/ByteTests.cs b/src/libraries/System.Runtime/tests/System/ByteTests.cs index b0b309e47892d..3593c9b4f470e 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(Encoding.UTF8.GetBytes(value), 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..a40541573c442 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(Encoding.UTF8.GetBytes(value), 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 7f09dd66ec678..6c8cfda43d565 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 @@ -621,6 +622,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(Encoding.UTF8.GetBytes(value), 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..b37476cb9ae63 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(Encoding.UTF8.GetBytes(value), 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..26fba082e64a8 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(Encoding.UTF8.GetBytes(value), 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..150b035dccb5f 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(Encoding.UTF8.GetBytes(value), 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..0b5f7c8250efe 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(Encoding.UTF8.GetBytes(value), 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..07b79d9433300 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(Encoding.UTF8.GetBytes(value), 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..f4a16159467f7 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(Encoding.UTF8.GetBytes(value), 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..0f18bb2e61e02 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(Encoding.UTF8.GetBytes(value), 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 49eaecf159285..176ffc0993cc9 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 @@ -560,6 +561,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(Encoding.UTF8.GetBytes(value), 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..f17444e678da0 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(Encoding.UTF8.GetBytes(value), 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..25b4c3b944678 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(Encoding.UTF8.GetBytes(value), 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..f912fbd03434a 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(Encoding.UTF8.GetBytes(value), 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..e47286dca51b2 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(Encoding.UTF8.GetBytes(value), 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..6e1f5d3b46094 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(Encoding.UTF8.GetBytes(value), 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) =>