|
// 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.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Unicode;
namespace System
{
// The Parse methods provided by the numeric classes convert a
// string to a numeric value. The optional style parameter specifies the
// permitted style of the numeric string. It must be a combination of bit flags
// from the NumberStyles enumeration. The optional info parameter
// specifies the NumberFormatInfo instance to use when parsing the
// string. If the info parameter is null or omitted, the numeric
// formatting information is obtained from the current culture.
//
// 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 parsable
// by the Parse methods if the NumberStyles.Any style is
// specified. Note, however, that the Parse methods do not accept
// NaNs or Infinities.
internal interface IBinaryIntegerParseAndFormatInfo<TSelf> : IBinaryInteger<TSelf>, IMinMaxValue<TSelf>
where TSelf : unmanaged, IBinaryIntegerParseAndFormatInfo<TSelf>
{
static abstract bool IsSigned { get; }
static abstract int MaxDigitCount { get; }
static abstract int MaxHexDigitCount { get; }
static abstract TSelf MaxValueDiv10 { get; }
static abstract string OverflowMessage { get; }
static abstract bool IsGreaterThanAsUnsigned(TSelf left, TSelf right);
static abstract TSelf MultiplyBy10(TSelf value);
static abstract TSelf MultiplyBy16(TSelf value);
}
internal interface IBinaryFloatParseAndFormatInfo<TSelf> : IBinaryFloatingPointIeee754<TSelf>, IMinMaxValue<TSelf>
where TSelf : unmanaged, IBinaryFloatParseAndFormatInfo<TSelf>
{
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;
private const int UInt32Precision = Int32Precision;
private const int Int64Precision = 19;
private const int UInt64Precision = 20;
private const int Int128Precision = 39;
private const int UInt128Precision = 39;
private const int FloatingPointMaxExponent = 309;
private const int FloatingPointMinExponent = -324;
private const int FloatingPointMaxDenormalMantissaBits = 52;
private static unsafe bool TryNumberBufferToBinaryInteger<TInteger>(ref NumberBuffer number, ref TInteger value)
where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo<TInteger>
{
number.CheckConsistency();
int i = number.Scale;
if ((i > TInteger.MaxDigitCount) || (i < number.DigitsCount) || (!TInteger.IsSigned && number.IsNegative))
{
return false;
}
byte* p = number.DigitsPtr;
Debug.Assert(p != null);
TInteger n = TInteger.Zero;
while (--i >= 0)
{
if (TInteger.IsGreaterThanAsUnsigned(n, TInteger.MaxValueDiv10))
{
return false;
}
n = TInteger.MultiplyBy10(n);
if (*p != '\0')
{
TInteger newN = n + TInteger.CreateTruncating(*p++ - '0');
if (!TInteger.IsSigned && (newN < n))
{
return false;
}
n = newN;
}
}
if (TInteger.IsSigned)
{
if (number.IsNegative)
{
n = -n;
if (n > TInteger.Zero)
{
return false;
}
}
else if (n < TInteger.Zero)
{
return false;
}
}
value = n;
return true;
}
internal static TInteger ParseBinaryInteger<TChar, TInteger>(ReadOnlySpan<TChar> value, NumberStyles styles, NumberFormatInfo info)
where TChar : unmanaged, IUtfChar<TChar>
where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo<TInteger>
{
ParsingStatus status = TryParseBinaryInteger(value, styles, info, out TInteger result);
if (status != ParsingStatus.OK)
{
ThrowOverflowOrFormatException<TChar, TInteger>(status, value);
}
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static ParsingStatus TryParseBinaryInteger<TChar, TInteger>(ReadOnlySpan<TChar> value, NumberStyles styles, NumberFormatInfo info, out TInteger result)
where TChar : unmanaged, IUtfChar<TChar>
where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo<TInteger>
{
if ((styles & ~NumberStyles.Integer) == 0)
{
// Optimized path for the common case of anything that's allowed for integer style.
return TryParseBinaryIntegerStyle(value, styles, info, out result);
}
if ((styles & NumberStyles.AllowHexSpecifier) != 0)
{
return TryParseBinaryIntegerHexNumberStyle(value, styles, out result);
}
if ((styles & NumberStyles.AllowBinarySpecifier) != 0)
{
return TryParseBinaryIntegerHexOrBinaryNumberStyle<TChar, TInteger, BinaryParser<TInteger>>(value, styles, out result);
}
return TryParseBinaryIntegerNumber(value, styles, info, out result);
}
private static ParsingStatus TryParseBinaryIntegerNumber<TChar, TInteger>(ReadOnlySpan<TChar> value, NumberStyles styles, NumberFormatInfo info, out TInteger result)
where TChar : unmanaged, IUtfChar<TChar>
where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo<TInteger>
{
result = TInteger.Zero;
NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, stackalloc byte[TInteger.MaxDigitCount + 1]);
if (!TryStringToNumber(value, styles, ref number, info))
{
return ParsingStatus.Failed;
}
if (!TryNumberBufferToBinaryInteger(ref number, ref result))
{
return ParsingStatus.Overflow;
}
return ParsingStatus.OK;
}
/// <summary>Parses int limited to styles that make up NumberStyles.Integer.</summary>
internal static ParsingStatus TryParseBinaryIntegerStyle<TChar, TInteger>(ReadOnlySpan<TChar> value, NumberStyles styles, NumberFormatInfo info, out TInteger result)
where TChar : unmanaged, IUtfChar<TChar>
where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo<TInteger>
{
Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format");
if (value.IsEmpty)
{
goto FalseExit;
}
int index = 0;
uint num = TChar.CastToUInt32(value[0]);
// Skip past any whitespace at the beginning.
if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num))
{
do
{
index++;
if ((uint)index >= (uint)value.Length)
{
goto FalseExit;
}
num = TChar.CastToUInt32(value[index]);
}
while (IsWhite(num));
}
// Parse leading sign.
bool isNegative = false;
if ((styles & NumberStyles.AllowLeadingSign) != 0)
{
if (info.HasInvariantNumberSigns)
{
if (num == '-')
{
isNegative = true;
index++;
if ((uint)index >= (uint)value.Length)
{
goto FalseExit;
}
num = TChar.CastToUInt32(value[index]);
}
else if (num == '+')
{
index++;
if ((uint)index >= (uint)value.Length)
{
goto FalseExit;
}
num = TChar.CastToUInt32(value[index]);
}
}
else if (info.AllowHyphenDuringParsing() && num == '-')
{
isNegative = true;
index++;
if ((uint)index >= (uint)value.Length)
{
goto FalseExit;
}
num = TChar.CastToUInt32(value[index]);
}
else
{
value = value.Slice(index);
index = 0;
ReadOnlySpan<TChar> positiveSign = info.PositiveSignTChar<TChar>();
ReadOnlySpan<TChar> negativeSign = info.NegativeSignTChar<TChar>();
if (!positiveSign.IsEmpty && value.StartsWith(positiveSign))
{
index += positiveSign.Length;
if ((uint)index >= (uint)value.Length)
{
goto FalseExit;
}
num = TChar.CastToUInt32(value[index]);
}
else if (!negativeSign.IsEmpty && value.StartsWith(negativeSign))
{
isNegative = true;
index += negativeSign.Length;
if ((uint)index >= (uint)value.Length)
{
goto FalseExit;
}
num = TChar.CastToUInt32(value[index]);
}
}
}
bool overflow = !TInteger.IsSigned && isNegative;
TInteger answer = TInteger.Zero;
if (IsDigit(num))
{
// Skip past leading zeros.
if (num == '0')
{
do
{
index++;
if ((uint)index >= (uint)value.Length)
{
goto DoneAtEnd;
}
num = TChar.CastToUInt32(value[index]);
} while (num == '0');
if (!IsDigit(num))
{
if (!TInteger.IsSigned)
{
overflow = false;
}
goto HasTrailingChars;
}
}
// 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 = TChar.CastToUInt32(value[index]);
if (!IsDigit(num))
{
goto HasTrailingChars;
}
index++;
answer = TInteger.MultiplyBy10(answer);
answer += TInteger.CreateTruncating(num - '0');
}
if ((uint)index >= (uint)value.Length)
{
if (!TInteger.IsSigned)
{
goto DoneAtEndButPotentialOverflow;
}
else
{
goto DoneAtEnd;
}
}
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'));
}
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 = TChar.CastToUInt32(value[index]);
while (IsDigit(num))
{
overflow = true;
index++;
if ((uint)index >= (uint)value.Length)
{
goto OverflowExit;
}
num = TChar.CastToUInt32(value[index]);
}
goto HasTrailingChars;
}
goto FalseExit;
DoneAtEndButPotentialOverflow:
if (overflow)
{
goto OverflowExit;
}
DoneAtEnd:
if (!TInteger.IsSigned)
{
result = answer;
}
else
{
result = isNegative ? -answer : answer;
}
ParsingStatus status = ParsingStatus.OK;
Exit:
return status;
FalseExit: // parsing failed
result = TInteger.Zero;
status = ParsingStatus.Failed;
goto Exit;
OverflowExit:
result = TInteger.Zero;
status = ParsingStatus.Overflow;
goto Exit;
HasTrailingChars: // we've successfully parsed, but there are still remaining characters in the span
// Skip past trailing whitespace, then past trailing zeros, and if anything else remains, fail.
if (IsWhite(num))
{
if ((styles & NumberStyles.AllowTrailingWhite) == 0)
{
goto FalseExit;
}
for (index++; index < value.Length; 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;
}
/// <summary>Parses <typeparamref name="TInteger"/> limited to styles that make up NumberStyles.HexNumber.</summary>
internal static ParsingStatus TryParseBinaryIntegerHexNumberStyle<TChar, TInteger>(ReadOnlySpan<TChar> value, NumberStyles styles, out TInteger result)
where TChar : unmanaged, IUtfChar<TChar>
where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo<TInteger>
{
return TryParseBinaryIntegerHexOrBinaryNumberStyle<TChar, TInteger, HexParser<TInteger>>(value, styles, out result);
}
private interface IHexOrBinaryParser<TInteger>
where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo<TInteger>
{
static abstract NumberStyles AllowedStyles { get; }
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);
}
private readonly struct HexParser<TInteger> : IHexOrBinaryParser<TInteger> where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo<TInteger>
{
public static NumberStyles AllowedStyles => NumberStyles.HexNumber;
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);
}
private readonly struct BinaryParser<TInteger> : IHexOrBinaryParser<TInteger> where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo<TInteger>
{
public static NumberStyles AllowedStyles => NumberStyles.BinaryNumber;
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<TChar, TInteger, TParser>(ReadOnlySpan<TChar> value, NumberStyles styles, out TInteger result)
where TChar : unmanaged, IUtfChar<TChar>
where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo<TInteger>
where TParser : struct, IHexOrBinaryParser<TInteger>
{
Debug.Assert((styles & ~TParser.AllowedStyles) == 0, $"Only handles subsets of {TParser.AllowedStyles} format");
if (value.IsEmpty)
{
goto FalseExit;
}
int index = 0;
uint num = TChar.CastToUInt32(value[0]);
// Skip past any whitespace at the beginning.
if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num))
{
do
{
index++;
if ((uint)index >= (uint)value.Length)
{
goto FalseExit;
}
num = TChar.CastToUInt32(value[index]);
}
while (IsWhite(num));
}
bool overflow = false;
TInteger answer = TInteger.Zero;
if (TParser.IsValidChar(num))
{
// Skip past leading zeros.
if (num == '0')
{
do
{
index++;
if ((uint)index >= (uint)value.Length)
{
goto DoneAtEnd;
}
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 = 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 = 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 = TChar.CastToUInt32(value[index]);
} while (TParser.IsValidChar(num));
overflow = true;
goto HasTrailingChars;
}
goto FalseExit;
DoneAtEndButPotentialOverflow:
if (overflow)
{
goto OverflowExit;
}
DoneAtEnd:
result = answer;
ParsingStatus status = ParsingStatus.OK;
Exit:
return status;
FalseExit: // parsing failed
result = TInteger.Zero;
status = ParsingStatus.Failed;
goto Exit;
OverflowExit:
result = TInteger.Zero;
status = ParsingStatus.Overflow;
goto Exit;
HasTrailingChars: // we've successfully parsed, but there are still remaining characters in the span
// Skip past trailing whitespace, then past trailing zeros, and if anything else remains, fail.
if (IsWhite(num))
{
if ((styles & NumberStyles.AllowTrailingWhite) == 0)
{
goto FalseExit;
}
for (index++; index < value.Length; 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;
}
internal static decimal ParseDecimal<TChar>(ReadOnlySpan<TChar> value, NumberStyles styles, NumberFormatInfo info)
where TChar : unmanaged, IUtfChar<TChar>
{
ParsingStatus status = TryParseDecimal(value, styles, info, out decimal result);
if (status != ParsingStatus.OK)
{
if (status == ParsingStatus.Failed)
{
ThrowFormatException(value);
}
ThrowOverflowException(SR.Overflow_Decimal);
}
return result;
}
internal static unsafe bool TryNumberToDecimal(ref NumberBuffer number, ref decimal value)
{
number.CheckConsistency();
byte* p = number.DigitsPtr;
int e = number.Scale;
bool sign = number.IsNegative;
uint c = *p;
if (c == 0)
{
// To avoid risking an app-compat issue with pre 4.5 (where some app was illegally using Reflection to examine the internal scale bits), we'll only force
// the scale to 0 if the scale was previously positive (previously, such cases were unparsable to a bug.)
value = new decimal(0, 0, 0, sign, (byte)Math.Clamp(-e, 0, 28));
return true;
}
if (e > DecimalPrecision)
return false;
ulong low64 = 0;
while (e > -28)
{
e--;
low64 *= 10;
low64 += c - '0';
c = *++p;
if (low64 >= ulong.MaxValue / 10)
break;
if (c == 0)
{
while (e > 0)
{
e--;
low64 *= 10;
if (low64 >= ulong.MaxValue / 10)
break;
}
break;
}
}
uint high = 0;
while ((e > 0 || (c != 0 && e > -28)) &&
(high < uint.MaxValue / 10 || (high == uint.MaxValue / 10 && (low64 < 0x99999999_99999999 || (low64 == 0x99999999_99999999 && c <= '5')))))
{
// multiply by 10
ulong tmpLow = (uint)low64 * 10UL;
ulong tmp64 = ((uint)(low64 >> 32) * 10UL) + (tmpLow >> 32);
low64 = (uint)tmpLow + (tmp64 << 32);
high = (uint)(tmp64 >> 32) + (high * 10);
if (c != 0)
{
c -= '0';
low64 += c;
if (low64 < c)
high++;
c = *++p;
}
e--;
}
if (c >= '5')
{
if ((c == '5') && ((low64 & 1) == 0))
{
c = *++p;
bool hasZeroTail = !number.HasNonZeroTail;
// We might still have some additional digits, in which case they need
// to be considered as part of hasZeroTail. Some examples of this are:
// * 3.0500000000000000000001e-27
// * 3.05000000000000000000001e-27
// In these cases, we will have processed 3 and 0, and ended on 5. The
// buffer, however, will still contain a number of trailing zeros and
// a trailing non-zero number.
while ((c != 0) && hasZeroTail)
{
hasZeroTail &= c == '0';
c = *++p;
}
// We should either be at the end of the stream or have a non-zero tail
Debug.Assert((c == 0) || !hasZeroTail);
if (hasZeroTail)
{
// When the next digit is 5, the number is even, and all following
// digits are zero we don't need to round.
goto NoRounding;
}
}
if (++low64 == 0 && ++high == 0)
{
low64 = 0x99999999_9999999A;
high = uint.MaxValue / 10;
e++;
}
}
NoRounding:
if (e > 0)
return false;
if (e <= -DecimalPrecision)
{
// Parsing a large scale zero can give you more precision than fits in the decimal.
// This should only happen for actual zeros or very small numbers that round to zero.
value = new decimal(0, 0, 0, sign, DecimalPrecision - 1);
}
else
{
value = new decimal((int)low64, (int)(low64 >> 32), (int)high, sign, (byte)-e);
}
return true;
}
internal static TFloat ParseFloat<TChar, TFloat>(ReadOnlySpan<TChar> value, NumberStyles styles, NumberFormatInfo info)
where TChar : unmanaged, IUtfChar<TChar>
where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo<TFloat>
{
if (!TryParseFloat(value, styles, info, out TFloat result))
{
ThrowFormatException(value);
}
return result;
}
internal static ParsingStatus TryParseDecimal<TChar>(ReadOnlySpan<TChar> value, NumberStyles styles, NumberFormatInfo info, out decimal result)
where TChar : unmanaged, IUtfChar<TChar>
{
NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, stackalloc byte[DecimalNumberBufferLength]);
result = 0;
if (!TryStringToNumber(value, styles, ref number, info))
{
return ParsingStatus.Failed;
}
if (!TryNumberToDecimal(ref number, ref result))
{
return ParsingStatus.Overflow;
}
return ParsingStatus.OK;
}
internal static bool SpanStartsWith<TChar>(ReadOnlySpan<TChar> span, TChar c)
where TChar : unmanaged, IUtfChar<TChar>
{
return !span.IsEmpty && (span[0] == c);
}
internal static bool SpanStartsWith<TChar>(ReadOnlySpan<TChar> span, ReadOnlySpan<TChar> value, StringComparison comparisonType)
where TChar : unmanaged, IUtfChar<TChar>
{
if (typeof(TChar) == typeof(char))
{
ReadOnlySpan<char> typedSpan = MemoryMarshal.Cast<TChar, char>(span);
ReadOnlySpan<char> typedValue = MemoryMarshal.Cast<TChar, char>(value);
return typedSpan.StartsWith(typedValue, comparisonType);
}
else
{
Debug.Assert(typeof(TChar) == typeof(byte));
ReadOnlySpan<byte> typedSpan = MemoryMarshal.Cast<TChar, byte>(span);
ReadOnlySpan<byte> typedValue = MemoryMarshal.Cast<TChar, byte>(value);
return typedSpan.StartsWithUtf8(typedValue, comparisonType);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static ReadOnlySpan<TChar> SpanTrim<TChar>(ReadOnlySpan<TChar> span)
where TChar : unmanaged, IUtfChar<TChar>
{
if (typeof(TChar) == typeof(char))
{
return MemoryMarshal.Cast<char, TChar>(MemoryMarshal.Cast<TChar, char>(span).Trim());
}
else
{
Debug.Assert(typeof(TChar) == typeof(byte));
return MemoryMarshal.Cast<byte, TChar>(MemoryMarshal.Cast<TChar, byte>(span).TrimUtf8());
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool SpanEqualsOrdinalIgnoreCase<TChar>(ReadOnlySpan<TChar> span, ReadOnlySpan<TChar> value)
where TChar : unmanaged, IUtfChar<TChar>
{
if (typeof(TChar) == typeof(char))
{
ReadOnlySpan<char> typedSpan = MemoryMarshal.Cast<TChar, char>(span);
ReadOnlySpan<char> typedValue = MemoryMarshal.Cast<TChar, char>(value);
return typedSpan.EqualsOrdinalIgnoreCase(typedValue);
}
else
{
Debug.Assert(typeof(TChar) == typeof(byte));
ReadOnlySpan<byte> typedSpan = MemoryMarshal.Cast<TChar, byte>(span);
ReadOnlySpan<byte> typedValue = MemoryMarshal.Cast<TChar, byte>(value);
return typedSpan.EqualsOrdinalIgnoreCaseUtf8(typedValue);
}
}
internal static bool TryParseFloat<TChar, TFloat>(ReadOnlySpan<TChar> value, NumberStyles styles, NumberFormatInfo info, out TFloat result)
where TChar : unmanaged, IUtfChar<TChar>
where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo<TFloat>
{
NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, stackalloc byte[TFloat.NumberBufferLength]);
if (!TryStringToNumber(value, styles, ref number, info))
{
ReadOnlySpan<TChar> valueTrim = SpanTrim(value);
// This code would be simpler if we only had the concept of `InfinitySymbol`, but
// we don't so we'll check the existing cases first and then handle `PositiveSign` +
// `PositiveInfinitySymbol` and `PositiveSign/NegativeSign` + `NaNSymbol` last.
ReadOnlySpan<TChar> positiveInfinitySymbol = info.PositiveInfinitySymbolTChar<TChar>();
if (SpanEqualsOrdinalIgnoreCase(valueTrim, positiveInfinitySymbol))
{
result = TFloat.PositiveInfinity;
return true;
}
if (SpanEqualsOrdinalIgnoreCase(valueTrim, info.NegativeInfinitySymbolTChar<TChar>()))
{
result = TFloat.NegativeInfinity;
return true;
}
ReadOnlySpan<TChar> nanSymbol = info.NaNSymbolTChar<TChar>();
if (SpanEqualsOrdinalIgnoreCase(valueTrim, nanSymbol))
{
result = TFloat.NaN;
return true;
}
var positiveSign = info.PositiveSignTChar<TChar>();
if (SpanStartsWith(valueTrim, positiveSign, StringComparison.OrdinalIgnoreCase))
{
valueTrim = valueTrim.Slice(positiveSign.Length);
if (SpanEqualsOrdinalIgnoreCase(valueTrim, positiveInfinitySymbol))
{
result = TFloat.PositiveInfinity;
return true;
}
else if (SpanEqualsOrdinalIgnoreCase(valueTrim, nanSymbol))
{
result = TFloat.NaN;
return true;
}
result = TFloat.Zero;
return false;
}
ReadOnlySpan<TChar> negativeSign = info.NegativeSignTChar<TChar>();
if (SpanStartsWith(valueTrim, negativeSign, StringComparison.OrdinalIgnoreCase))
{
if (SpanEqualsOrdinalIgnoreCase(valueTrim.Slice(negativeSign.Length), nanSymbol))
{
result = TFloat.NaN;
return true;
}
if (info.AllowHyphenDuringParsing() && SpanStartsWith(valueTrim, TChar.CastFrom('-')) && SpanEqualsOrdinalIgnoreCase(valueTrim.Slice(1), nanSymbol))
{
result = TFloat.NaN;
return true;
}
}
result = TFloat.Zero;
return false; // We really failed
}
result = NumberToFloat<TFloat>(ref number);
return true;
}
[DoesNotReturn]
internal static void ThrowOverflowOrFormatException<TChar, TInteger>(ParsingStatus status, ReadOnlySpan<TChar> value)
where TChar : unmanaged, IUtfChar<TChar>
where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo<TInteger>
{
if (status == ParsingStatus.Failed)
{
ThrowFormatException(value);
}
ThrowOverflowException<TInteger>();
}
[DoesNotReturn]
internal static void ThrowFormatException<TChar>(ReadOnlySpan<TChar> value)
where TChar : unmanaged, IUtfChar<TChar>
{
string errorMessage;
if (typeof(TChar) == typeof(byte))
{
// Decode the UTF8 value into a string we can include in the error message. We're here
// because we failed to parse, which also means the bytes might not be valid UTF8,
// so fallback to a message that doesn't include the value if the bytes are invalid.
// It's possible after we check the bytes for validity that they could be concurrently
// mutated, but if that's happening, all bets are off, anyway, and it simply impacts
// which exception is thrown.
ReadOnlySpan<byte> bytes = MemoryMarshal.Cast<TChar, byte>(value);
errorMessage = Utf8.IsValid(bytes) ?
SR.Format(SR.Format_InvalidStringWithValue, Encoding.UTF8.GetString(bytes)) :
SR.Format_InvalidString;
}
else
{
errorMessage = SR.Format(SR.Format_InvalidStringWithValue, value.ToString());
}
throw new FormatException(errorMessage);
}
[DoesNotReturn]
internal static void ThrowOverflowException<TInteger>()
where TInteger : unmanaged, IBinaryIntegerParseAndFormatInfo<TInteger>
{
throw new OverflowException(TInteger.OverflowMessage);
}
[DoesNotReturn]
internal static void ThrowOverflowException(string message)
{
throw new OverflowException(message);
}
internal static TFloat NumberToFloat<TFloat>(ref NumberBuffer number)
where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo<TFloat>
{
number.CheckConsistency();
TFloat result;
if ((number.DigitsCount == 0) || (number.Scale < TFloat.MinDecimalExponent))
{
result = TFloat.Zero;
}
else if (number.Scale > TFloat.MaxDecimalExponent)
{
result = TFloat.PositiveInfinity;
}
else
{
ulong bits = NumberToFloatingPointBits<TFloat>(ref number);
result = TFloat.BitsToFloat(bits);
}
return number.IsNegative ? -result : result;
}
}
}
|