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 439ee87e6868f1..005c30cb021db7 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 37f85f2553333a..2e361ed42dcc0b 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 e778864eedb92f..31a2bccf21bf88 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 b7e2488712cfcc..5b75e639ae6f51 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 74e2a0d246b3eb..179e864e952942 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 b6c6c0704ace18..e5c40133c690f7 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 48067b789891bc..ba621512477f45 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 bb7fbaa73fffa3..99d7cc3dd0e45e 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 00000000000000..873aaca0094ec8
--- /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 00000000000000..e1e708705bb137
--- /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 40f68fa3b9b38d..36854cd07bab7f 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 91bcd1687bb674..58a7b7865ef825 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 00000000000000..3aaf39df1236df
--- /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 09d8ba184436f1..25f371ada62132 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 33746544708655..50dce177e17135 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 7b4261e77882a8..af144fb860c921 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 7660fbaaae8b3c..2f91c2c2a59702 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 cf8bf7850790f3..ca170d8c903ed0 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 3c6434b0d2ce97..3dd33c2c66b82d 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 00000000000000..c9690c96954baa
--- /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 00000000000000..03c7acfcb2b8ee
--- /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 ed689b5d9ec774..a67c7f78aa4793 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 740ec52a78b90c..24e809073f0a36 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 ff94db3bcf9c80..77164243594258 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