From 91123521345af0da226b730d48cb0f445e2a56a0 Mon Sep 17 00:00:00 2001 From: Pent Ploompuu Date: Wed, 12 Dec 2018 16:47:16 +0200 Subject: [PATCH] Optimize number parsing (dotnet/coreclr#21365) Commit migrated from https://github.com/dotnet/coreclr/commit/4d7f7112efaf54a9634abf2f3a8fecc254301c85 --- .../src/System.Private.CoreLib/src/System/Enum.cs | 96 ++-- .../System.Private.CoreLib/src/System/Byte.cs | 22 +- .../src/System/Decimal.DecCalc.cs | 17 +- .../System.Private.CoreLib/src/System/Decimal.cs | 36 +- .../src/System/Globalization/NumberFormatInfo.cs | 72 +-- .../System.Private.CoreLib/src/System/Guid.cs | 4 +- .../System.Private.CoreLib/src/System/Int16.cs | 51 +- .../System.Private.CoreLib/src/System/Int32.cs | 8 +- .../System.Private.CoreLib/src/System/Int64.cs | 8 +- .../src/System/Number.Parsing.cs | 541 +++++++++++---------- .../src/System/ParseNumbers.cs | 29 +- .../System.Private.CoreLib/src/System/SByte.cs | 47 +- .../System.Private.CoreLib/src/System/UInt16.cs | 22 +- .../System.Private.CoreLib/src/System/UInt32.cs | 8 +- .../System.Private.CoreLib/src/System/UInt64.cs | 8 +- 15 files changed, 491 insertions(+), 478 deletions(-) diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Enum.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Enum.cs index 44a52ab..c8c1fa1 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Enum.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Enum.cs @@ -402,32 +402,32 @@ namespace System switch (Type.GetTypeCode(rt)) { case TypeCode.SByte: - parsed = TryParseInt32Enum(rt, value, valueSpan, sbyte.MinValue, sbyte.MaxValue, ignoreCase, throwOnFailure, nameof(SR.Overflow_SByte), out intResult); + parsed = TryParseInt32Enum(rt, value, valueSpan, sbyte.MinValue, sbyte.MaxValue, ignoreCase, throwOnFailure, TypeCode.SByte, out intResult); result = parsed ? InternalBoxEnum(rt, intResult) : null; return parsed; case TypeCode.Int16: - parsed = TryParseInt32Enum(rt, value, valueSpan, short.MinValue, short.MaxValue, ignoreCase, throwOnFailure, nameof(SR.Overflow_Int16), out intResult); + parsed = TryParseInt32Enum(rt, value, valueSpan, short.MinValue, short.MaxValue, ignoreCase, throwOnFailure, TypeCode.Int16, out intResult); result = parsed ? InternalBoxEnum(rt, intResult) : null; return parsed; case TypeCode.Int32: - parsed = TryParseInt32Enum(rt, value, valueSpan, int.MinValue, int.MaxValue, ignoreCase, throwOnFailure, nameof(SR.Overflow_Int32), out intResult); + parsed = TryParseInt32Enum(rt, value, valueSpan, int.MinValue, int.MaxValue, ignoreCase, throwOnFailure, TypeCode.Int32, out intResult); result = parsed ? InternalBoxEnum(rt, intResult) : null; return parsed; case TypeCode.Byte: - parsed = TryParseUInt32Enum(rt, value, valueSpan, byte.MaxValue, ignoreCase, throwOnFailure, nameof(SR.Overflow_Byte), out uintResult); + parsed = TryParseUInt32Enum(rt, value, valueSpan, byte.MaxValue, ignoreCase, throwOnFailure, TypeCode.Byte, out uintResult); result = parsed ? InternalBoxEnum(rt, uintResult) : null; return parsed; case TypeCode.UInt16: - parsed = TryParseUInt32Enum(rt, value, valueSpan, ushort.MaxValue, ignoreCase, throwOnFailure, nameof(SR.Overflow_UInt16), out uintResult); + parsed = TryParseUInt32Enum(rt, value, valueSpan, ushort.MaxValue, ignoreCase, throwOnFailure, TypeCode.UInt16, out uintResult); result = parsed ? InternalBoxEnum(rt, uintResult) : null; return parsed; case TypeCode.UInt32: - parsed = TryParseUInt32Enum(rt, value, valueSpan, uint.MaxValue, ignoreCase, throwOnFailure, nameof(SR.Overflow_UInt32), out uintResult); + parsed = TryParseUInt32Enum(rt, value, valueSpan, uint.MaxValue, ignoreCase, throwOnFailure, TypeCode.UInt32, out uintResult); result = parsed ? InternalBoxEnum(rt, uintResult) : null; return parsed; @@ -482,36 +482,36 @@ namespace System switch (Type.GetTypeCode(typeof(TEnum))) { case TypeCode.SByte: - parsed = TryParseInt32Enum(rt, value, valueSpan, sbyte.MinValue, sbyte.MaxValue, ignoreCase, throwOnFailure, nameof(SR.Overflow_SByte), out intResult); + parsed = TryParseInt32Enum(rt, value, valueSpan, sbyte.MinValue, sbyte.MaxValue, ignoreCase, throwOnFailure, TypeCode.SByte, out intResult); sbyte sbyteResult = (sbyte)intResult; result = Unsafe.As(ref sbyteResult); return parsed; case TypeCode.Int16: - parsed = TryParseInt32Enum(rt, value, valueSpan, short.MinValue, short.MaxValue, ignoreCase, throwOnFailure, nameof(SR.Overflow_Int16), out intResult); + parsed = TryParseInt32Enum(rt, value, valueSpan, short.MinValue, short.MaxValue, ignoreCase, throwOnFailure, TypeCode.Int16, out intResult); short shortResult = (short)intResult; result = Unsafe.As(ref shortResult); return parsed; case TypeCode.Int32: - parsed = TryParseInt32Enum(rt, value, valueSpan, int.MinValue, int.MaxValue, ignoreCase, throwOnFailure, nameof(SR.Overflow_Int32), out intResult); + parsed = TryParseInt32Enum(rt, value, valueSpan, int.MinValue, int.MaxValue, ignoreCase, throwOnFailure, TypeCode.Int32, out intResult); result = Unsafe.As(ref intResult); return parsed; case TypeCode.Byte: - parsed = TryParseUInt32Enum(rt, value, valueSpan, byte.MaxValue, ignoreCase, throwOnFailure, nameof(SR.Overflow_Byte), out uintResult); + parsed = TryParseUInt32Enum(rt, value, valueSpan, byte.MaxValue, ignoreCase, throwOnFailure, TypeCode.Byte, out uintResult); byte byteResult = (byte)uintResult; result = Unsafe.As(ref byteResult); return parsed; case TypeCode.UInt16: - parsed = TryParseUInt32Enum(rt, value, valueSpan, ushort.MaxValue, ignoreCase, throwOnFailure, nameof(SR.Overflow_UInt16), out uintResult); + parsed = TryParseUInt32Enum(rt, value, valueSpan, ushort.MaxValue, ignoreCase, throwOnFailure, TypeCode.UInt16, out uintResult); ushort ushortResult = (ushort)uintResult; result = Unsafe.As(ref ushortResult); return parsed; case TypeCode.UInt32: - parsed = TryParseUInt32Enum(rt, value, valueSpan, uint.MaxValue, ignoreCase, throwOnFailure, nameof(SR.Overflow_UInt32), out uintResult); + parsed = TryParseUInt32Enum(rt, value, valueSpan, uint.MaxValue, ignoreCase, throwOnFailure, TypeCode.UInt32, out uintResult); result = Unsafe.As(ref uintResult); return parsed; @@ -534,29 +534,33 @@ namespace System /// Tries to parse the value of an enum with known underlying types that fit in an Int32 (Int32, Int16, and SByte). private static bool TryParseInt32Enum( - RuntimeType enumType, string originalValueString, ReadOnlySpan value, int minInclusive, int maxInclusive, bool ignoreCase, bool throwOnFailure, string overflowFailureResourceName, out int result) + RuntimeType enumType, string originalValueString, ReadOnlySpan value, int minInclusive, int maxInclusive, bool ignoreCase, bool throwOnFailure, TypeCode type, out int result) { Debug.Assert( enumType.GetEnumUnderlyingType() == typeof(sbyte) || enumType.GetEnumUnderlyingType() == typeof(short) || enumType.GetEnumUnderlyingType() == typeof(int)); - bool failureIsOverflow = false; - if (StartsNumber(value[0]) && Number.TryParseInt32IntegerStyle(value, NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingWhite, CultureInfo.InvariantCulture.NumberFormat, out result, ref failureIsOverflow)) + Number.ParsingStatus status = default; + if (StartsNumber(value[0])) { - if ((uint)(result - minInclusive) <= (uint)(maxInclusive - minInclusive)) + status = Number.TryParseInt32IntegerStyle(value, NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingWhite, CultureInfo.InvariantCulture.NumberFormat, out result); + if (status == Number.ParsingStatus.OK) { - return true; - } + if ((uint)(result - minInclusive) <= (uint)(maxInclusive - minInclusive)) + { + return true; + } - failureIsOverflow = true; + status = Number.ParsingStatus.Overflow; + } } - if (failureIsOverflow) + if (status == Number.ParsingStatus.Overflow) { if (throwOnFailure) { - throw new OverflowException(SR.GetResourceString(overflowFailureResourceName)); + Number.ThrowOverflowException(type); } } else if (TryParseByName(enumType, originalValueString, value, ignoreCase, throwOnFailure, out ulong ulongResult)) @@ -571,29 +575,33 @@ namespace System } /// Tries to parse the value of an enum with known underlying types that fit in a UInt32 (UInt32, UInt16, and Byte). - private static bool TryParseUInt32Enum(RuntimeType enumType, string originalValueString, ReadOnlySpan value, uint maxInclusive, bool ignoreCase, bool throwOnFailure, string overflowFailureResourceName, out uint result) + private static bool TryParseUInt32Enum(RuntimeType enumType, string originalValueString, ReadOnlySpan value, uint maxInclusive, bool ignoreCase, bool throwOnFailure, TypeCode type, out uint result) { Debug.Assert( enumType.GetEnumUnderlyingType() == typeof(byte) || enumType.GetEnumUnderlyingType() == typeof(ushort) || enumType.GetEnumUnderlyingType() == typeof(uint)); - bool failureIsOverflow = false; - if (StartsNumber(value[0]) && Number.TryParseUInt32IntegerStyle(value, NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingWhite, CultureInfo.InvariantCulture.NumberFormat, out result, ref failureIsOverflow)) + Number.ParsingStatus status = default; + if (StartsNumber(value[0])) { - if (result <= maxInclusive) + status = Number.TryParseUInt32IntegerStyle(value, NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingWhite, CultureInfo.InvariantCulture.NumberFormat, out result); + if (status == Number.ParsingStatus.OK) { - return true; - } + if (result <= maxInclusive) + { + return true; + } - failureIsOverflow = true; + status = Number.ParsingStatus.Overflow; + } } - if (failureIsOverflow) + if (status == Number.ParsingStatus.Overflow) { if (throwOnFailure) { - throw new OverflowException(SR.GetResourceString(overflowFailureResourceName)); + Number.ThrowOverflowException(type); } } else if (TryParseByName(enumType, originalValueString, value, ignoreCase, throwOnFailure, out ulong ulongResult)) @@ -612,17 +620,21 @@ namespace System { Debug.Assert(enumType.GetEnumUnderlyingType() == typeof(long)); - bool failureIsOverflow = false; - if (StartsNumber(value[0]) && Number.TryParseInt64IntegerStyle(value, NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingWhite, CultureInfo.InvariantCulture.NumberFormat, out result, ref failureIsOverflow)) + Number.ParsingStatus status = default; + if (StartsNumber(value[0])) { - return true; + status = Number.TryParseInt64IntegerStyle(value, NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingWhite, CultureInfo.InvariantCulture.NumberFormat, out result); + if (status == Number.ParsingStatus.OK) + { + return true; + } } - if (failureIsOverflow) + if (status == Number.ParsingStatus.Overflow) { if (throwOnFailure) { - throw new OverflowException(SR.Overflow_Int64); + Number.ThrowOverflowException(TypeCode.Int64); } } else if (TryParseByName(enumType, originalValueString, value, ignoreCase, throwOnFailure, out ulong ulongResult)) @@ -640,17 +652,21 @@ namespace System { Debug.Assert(enumType.GetEnumUnderlyingType() == typeof(ulong)); - bool failureIsOverflow = false; - if (StartsNumber(value[0]) && Number.TryParseUInt64IntegerStyle(value, NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingWhite, CultureInfo.InvariantCulture.NumberFormat, out result, ref failureIsOverflow)) + Number.ParsingStatus status = default; + if (StartsNumber(value[0])) { - return true; + status = Number.TryParseUInt64IntegerStyle(value, NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingWhite, CultureInfo.InvariantCulture.NumberFormat, out result); + if (status == Number.ParsingStatus.OK) + { + return true; + } } - if (failureIsOverflow) + if (status == Number.ParsingStatus.Overflow) { if (throwOnFailure) { - throw new OverflowException(SR.Overflow_UInt64); + Number.ThrowOverflowException(TypeCode.UInt64); } } else if (TryParseByName(enumType, originalValueString, value, ignoreCase, throwOnFailure, out result)) diff --git a/src/libraries/System.Private.CoreLib/src/System/Byte.cs b/src/libraries/System.Private.CoreLib/src/System/Byte.cs index 4004cf1..697ad29 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Byte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Byte.cs @@ -107,17 +107,13 @@ namespace System private static byte Parse(ReadOnlySpan s, NumberStyles style, NumberFormatInfo info) { - int i = 0; - try + Number.ParsingStatus status = Number.TryParseUInt32(s, style, info, out uint i); + if (status != Number.ParsingStatus.OK) { - i = Number.ParseInt32(s, style, info); - } - catch (OverflowException e) - { - throw new OverflowException(SR.Overflow_Byte, e); + Number.ThrowOverflowOrFormatException(status, TypeCode.Byte); } - if (i < MinValue || i > MaxValue) throw new OverflowException(SR.Overflow_Byte); + if (i > MaxValue) Number.ThrowOverflowException(TypeCode.Byte); return (byte)i; } @@ -158,14 +154,10 @@ namespace System private static bool TryParse(ReadOnlySpan s, NumberStyles style, NumberFormatInfo info, out byte result) { - result = 0; - int i; - if (!Number.TryParseInt32(s, style, info, out i, out _)) - { - return false; - } - if (i < MinValue || i > MaxValue) + if (Number.TryParseUInt32(s, style, info, out uint i) != Number.ParsingStatus.OK + || i > MaxValue) { + result = 0; return false; } result = (byte)i; 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 ee27231..774c5d9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Decimal.DecCalc.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Decimal.DecCalc.cs @@ -202,7 +202,7 @@ namespace System high++; if (high > uint.MaxValue) - throw new OverflowException(SR.Overflow_Decimal); + Number.ThrowOverflowException(TypeCode.Decimal); result.Low64 = low; result.High = (uint)high; } @@ -687,7 +687,8 @@ PosRem: return scale; ThrowOverflow: - throw new OverflowException(SR.Overflow_Decimal); + Number.ThrowOverflowException(TypeCode.Decimal); + return 0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -758,7 +759,7 @@ ThrowOverflow: private static int OverflowUnscale(ref Buf12 bufQuo, int scale, bool sticky) { if (--scale < 0) - throw new OverflowException(SR.Overflow_Decimal); + Number.ThrowOverflowException(TypeCode.Decimal); Debug.Assert(bufQuo.U2 == 0); @@ -870,7 +871,7 @@ ThrowOverflow: // positive if it isn't already. // if (curScale + scale < 0) - throw new OverflowException(SR.Overflow_Decimal); + Number.ThrowOverflowException(TypeCode.Decimal); return curScale; } @@ -1138,7 +1139,7 @@ AlignedScale: // Divide the value by 10, dropping the scale factor. // if ((flags & ScaleMask) == 0) - throw new OverflowException(SR.Overflow_Decimal); + Number.ThrowOverflowException(TypeCode.Decimal); flags -= 1 << ScaleShift; const uint den = 10; @@ -1578,7 +1579,7 @@ ReturnZero: return; // result should be zeroed out if (exp > 96) - throw new OverflowException(SR.Overflow_Decimal); + Number.ThrowOverflowException(TypeCode.Decimal); uint flags = 0; if (input < 0) @@ -1745,7 +1746,7 @@ ReturnZero: return; // result should be zeroed out if (exp > 96) - throw new OverflowException(SR.Overflow_Decimal); + Number.ThrowOverflowException(TypeCode.Decimal); uint flags = 0; if (input < 0) @@ -2213,7 +2214,7 @@ RoundUp: } ThrowOverflow: - throw new OverflowException(SR.Overflow_Decimal); + Number.ThrowOverflowException(TypeCode.Decimal); } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs index a82ca0d..8a9f0d4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Decimal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Decimal.cs @@ -487,12 +487,12 @@ namespace System return false; } - return Number.TryParseDecimal(s, NumberStyles.Number, NumberFormatInfo.CurrentInfo, out result, out _); + return Number.TryParseDecimal(s, NumberStyles.Number, NumberFormatInfo.CurrentInfo, out result) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, out decimal result) { - return Number.TryParseDecimal(s, NumberStyles.Number, NumberFormatInfo.CurrentInfo, out result, out _); + return Number.TryParseDecimal(s, NumberStyles.Number, NumberFormatInfo.CurrentInfo, out result) == Number.ParsingStatus.OK; } public static bool TryParse(string s, NumberStyles style, IFormatProvider provider, out decimal result) @@ -505,13 +505,13 @@ namespace System return false; } - return Number.TryParseDecimal(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); + return Number.TryParseDecimal(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider provider, out decimal result) { NumberFormatInfo.ValidateParseStyleFloatingPoint(style); - return Number.TryParseDecimal(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); + return Number.TryParseDecimal(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; } // Returns a binary representation of a Decimal. The return value is an @@ -646,11 +646,12 @@ namespace System { temp = ToUInt32(value); } - catch (OverflowException e) + catch (OverflowException) { - throw new OverflowException(SR.Overflow_Byte, e); + Number.ThrowOverflowException(TypeCode.Byte); + throw; } - if (temp != (byte)temp) throw new OverflowException(SR.Overflow_Byte); + if (temp != (byte)temp) Number.ThrowOverflowException(TypeCode.Byte); return (byte)temp; } @@ -666,11 +667,12 @@ namespace System { temp = ToInt32(value); } - catch (OverflowException e) + catch (OverflowException) { - throw new OverflowException(SR.Overflow_SByte, e); + Number.ThrowOverflowException(TypeCode.SByte); + throw; } - if (temp != (sbyte)temp) throw new OverflowException(SR.Overflow_SByte); + if (temp != (sbyte)temp) Number.ThrowOverflowException(TypeCode.SByte); return (sbyte)temp; } @@ -685,11 +687,12 @@ namespace System { temp = ToInt32(value); } - catch (OverflowException e) + catch (OverflowException) { - throw new OverflowException(SR.Overflow_Int16, e); + Number.ThrowOverflowException(TypeCode.Int16); + throw; } - if (temp != (short)temp) throw new OverflowException(SR.Overflow_Int16); + if (temp != (short)temp) Number.ThrowOverflowException(TypeCode.Int16); return (short)temp; } @@ -759,11 +762,12 @@ namespace System { temp = ToUInt32(value); } - catch (OverflowException e) + catch (OverflowException) { - throw new OverflowException(SR.Overflow_UInt16, e); + Number.ThrowOverflowException(TypeCode.UInt16); + throw; } - if (temp != (ushort)temp) throw new OverflowException(SR.Overflow_UInt16); + if (temp != (ushort)temp) Number.ThrowOverflowException(TypeCode.UInt16); return (ushort)temp; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs index 7afe094..8fa6127 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Text; namespace System.Globalization { @@ -39,16 +37,11 @@ namespace System.Globalization // CurrencySymbol "$" String used as local monetary symbol. // - sealed public class NumberFormatInfo : IFormatProvider, ICloneable + public sealed class NumberFormatInfo : IFormatProvider, ICloneable { // invariantInfo is constant irrespective of your current culture. private static volatile NumberFormatInfo s_invariantInfo; - // READTHIS READTHIS READTHIS - // This class has an exact mapping onto a native structure defined in COMNumber.cpp - // DO NOT UPDATE THIS WITHOUT UPDATING THAT STRUCTURE. IF YOU ADD BOOL, ADD THEM AT THE END. - // ALSO MAKE SURE TO UPDATE mscorlib.h in the VM directory to check field offsets. - // READTHIS READTHIS READTHIS internal int[] numberGroupSizes = new int[] { 3 }; internal int[] currencyGroupSizes = new int[] { 3 }; internal int[] percentGroupSizes = new int[] { 3 }; @@ -83,10 +76,9 @@ namespace System.Globalization internal bool isReadOnly = false; - // Is this NumberFormatInfo for invariant culture? - internal bool m_isInvariant = false; + private bool _hasInvariantNumberSigns = true; - public NumberFormatInfo() : this(null) + public NumberFormatInfo() { } @@ -171,6 +163,13 @@ namespace System.Globalization } } + internal bool HasInvariantNumberSigns => _hasInvariantNumberSigns; + + private void UpdateHasInvariantNumberSigns() + { + _hasInvariantNumberSigns = positiveSign == "+" && negativeSign == "-"; + } + internal NumberFormatInfo(CultureData cultureData) { if (cultureData != null) @@ -179,11 +178,7 @@ namespace System.Globalization // don't need to verify their values (except for invalid parsing situations). cultureData.GetNFIValues(this); - if (cultureData.IsInvariantCulture) - { - // For invariant culture - this.m_isInvariant = true; - } + UpdateHasInvariantNumberSigns(); } } @@ -208,9 +203,10 @@ namespace System.Globalization { // Lazy create the invariant info. This cannot be done in a .cctor because exceptions can // be thrown out of a .cctor stack that will need this. - NumberFormatInfo nfi = new NumberFormatInfo(); - nfi.m_isInvariant = true; - s_invariantInfo = ReadOnly(nfi); + s_invariantInfo = new NumberFormatInfo + { + isReadOnly = true + }; } return s_invariantInfo; } @@ -568,6 +564,7 @@ namespace System.Globalization } VerifyWritable(); negativeSign = value; + UpdateHasInvariantNumberSigns(); } } @@ -669,6 +666,7 @@ namespace System.Globalization } VerifyWritable(); positiveSign = value; + UpdateHasInvariantNumberSigns(); } } @@ -803,15 +801,18 @@ namespace System.Globalization internal static void ValidateParseStyleInteger(NumberStyles style) { - // Check for undefined flags - if ((style & InvalidNumberStyles) != 0) + // Check for undefined flags or invalid hex number flags + if ((style & (InvalidNumberStyles | NumberStyles.AllowHexSpecifier)) != 0 + && (style & ~NumberStyles.HexNumber) != 0) { - throw new ArgumentException(SR.Argument_InvalidNumberStyles, nameof(style)); - } - if ((style & NumberStyles.AllowHexSpecifier) != 0) - { // Check for hex number - if ((style & ~NumberStyles.HexNumber) != 0) + throwInvalid(style); + + void throwInvalid(NumberStyles value) { + if ((value & InvalidNumberStyles) != 0) + { + throw new ArgumentException(SR.Argument_InvalidNumberStyles, nameof(style)); + } throw new ArgumentException(SR.Arg_InvalidHexStyle); } } @@ -819,14 +820,19 @@ namespace System.Globalization internal static void ValidateParseStyleFloatingPoint(NumberStyles style) { - // Check for undefined flags - if ((style & InvalidNumberStyles) != 0) + // Check for undefined flags or hex number + if ((style & (InvalidNumberStyles | NumberStyles.AllowHexSpecifier)) != 0) { - throw new ArgumentException(SR.Argument_InvalidNumberStyles, nameof(style)); - } - if ((style & NumberStyles.AllowHexSpecifier) != 0) - { // Check for hex number - throw new ArgumentException(SR.Arg_HexStyleNotSupported); + throwInvalid(style); + + void throwInvalid(NumberStyles value) + { + if ((value & InvalidNumberStyles) != 0) + { + throw new ArgumentException(SR.Argument_InvalidNumberStyles, nameof(style)); + } + throw new ArgumentException(SR.Arg_HexStyleNotSupported); + } } } } // NumberFormatInfo diff --git a/src/libraries/System.Private.CoreLib/src/System/Guid.cs b/src/libraries/System.Private.CoreLib/src/System/Guid.cs index be95bff..b241818 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Guid.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Guid.cs @@ -716,13 +716,13 @@ namespace System for (; i < guidString.Length && guidString[i] == '0'; i++); int processedDigits = 0; - int[] charToHexLookup = Number.s_charToHexLookup; + ReadOnlySpan charToHexLookup = Number.CharToHexLookup; uint tmp = 0; for (; i < guidString.Length; i++) { int numValue; char c = guidString[i]; - if (c >= charToHexLookup.Length || (numValue = charToHexLookup[c]) == 0xFF) + if (c >= (uint)charToHexLookup.Length || (numValue = charToHexLookup[c]) == 0xFF) { if (processedDigits > 8) overflow = true; result = 0; diff --git a/src/libraries/System.Private.CoreLib/src/System/Int16.cs b/src/libraries/System.Private.CoreLib/src/System/Int16.cs index a88d477..bb5a91b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int16.cs @@ -137,28 +137,18 @@ namespace System private static short Parse(ReadOnlySpan s, NumberStyles style, NumberFormatInfo info) { - int i = 0; - try + Number.ParsingStatus status = Number.TryParseInt32(s, style, info, out int i); + if (status != Number.ParsingStatus.OK) { - i = Number.ParseInt32(s, style, info); - } - catch (OverflowException e) - { - throw new OverflowException(SR.Overflow_Int16, e); + Number.ThrowOverflowOrFormatException(status, TypeCode.Int16); } - // We need this check here since we don't allow signs to specified in hex numbers. So we fixup the result - // for negative numbers - if ((style & NumberStyles.AllowHexSpecifier) != 0) - { // We are parsing a hexadecimal number - if ((i < 0) || (i > ushort.MaxValue)) - { - throw new OverflowException(SR.Overflow_Int16); - } - return (short)i; + // For hex number styles AllowHexSpecifier << 6 == 0x8000 and cancels out MinValue so the check is effectively: (uint)i > ushort.MaxValue + // For integer styles it's zero and the effective check is (uint)(i - MinValue) > ushort.MaxValue + if ((uint)(i - MinValue - ((int)(style & NumberStyles.AllowHexSpecifier) << 6)) > ushort.MaxValue) + { + Number.ThrowOverflowException(TypeCode.Int16); } - - if (i < MinValue || i > MaxValue) throw new OverflowException(SR.Overflow_Int16); return (short)i; } @@ -199,27 +189,12 @@ namespace System private static bool TryParse(ReadOnlySpan s, NumberStyles style, NumberFormatInfo info, out short result) { - result = 0; - int i; - if (!Number.TryParseInt32(s, style, info, out i, out _)) - { - return false; - } - - // We need this check here since we don't allow signs to specified in hex numbers. So we fixup the result - // for negative numbers - if ((style & NumberStyles.AllowHexSpecifier) != 0) - { // We are parsing a hexadecimal number - if ((i < 0) || i > ushort.MaxValue) - { - return false; - } - result = (short)i; - return true; - } - - if (i < MinValue || i > MaxValue) + // For hex number styles AllowHexSpecifier << 6 == 0x8000 and cancels out MinValue so the check is effectively: (uint)i > ushort.MaxValue + // For integer styles it's zero and the effective check is (uint)(i - MinValue) > ushort.MaxValue + if (Number.TryParseInt32(s, style, info, out int i) != Number.ParsingStatus.OK + || (uint)(i - MinValue - ((int)(style & NumberStyles.AllowHexSpecifier) << 6)) > ushort.MaxValue) { + result = 0; return false; } result = (short)i; diff --git a/src/libraries/System.Private.CoreLib/src/System/Int32.cs b/src/libraries/System.Private.CoreLib/src/System/Int32.cs index 5aa9753..0688f89 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int32.cs @@ -152,12 +152,12 @@ namespace System return false; } - return Number.TryParseInt32(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result, out _); + return Number.TryParseInt32IntegerStyle(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, out int result) { - return Number.TryParseInt32(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result, out _); + return Number.TryParseInt32IntegerStyle(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result) == Number.ParsingStatus.OK; } // Parses an integer from a String in the given style. Returns false rather @@ -173,13 +173,13 @@ namespace System return false; } - return Number.TryParseInt32(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); + return Number.TryParseInt32(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider provider, out int result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseInt32(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); + return Number.TryParseInt32(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; } // diff --git a/src/libraries/System.Private.CoreLib/src/System/Int64.cs b/src/libraries/System.Private.CoreLib/src/System/Int64.cs index 78e8352..b7722fe 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Int64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Int64.cs @@ -143,12 +143,12 @@ namespace System return false; } - return Number.TryParseInt64(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result, out _); + return Number.TryParseInt64IntegerStyle(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, out long result) { - return Number.TryParseInt64(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result, out _); + return Number.TryParseInt64IntegerStyle(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result) == Number.ParsingStatus.OK; } public static bool TryParse(string s, NumberStyles style, IFormatProvider provider, out long result) @@ -161,13 +161,13 @@ namespace System return false; } - return Number.TryParseInt64(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); + return Number.TryParseInt64(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; } public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider provider, out long result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseInt64(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); + return Number.TryParseInt64(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; } // 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 1ef1879..0d3c4028 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Globalization; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Internal.Runtime.CompilerServices; @@ -31,8 +32,8 @@ namespace System private const int Int64Precision = 19; private const int UInt64Precision = 20; - /// 256-element map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit. - internal static readonly int[] s_charToHexLookup = + /// Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit. + internal static ReadOnlySpan CharToHexLookup => new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31 @@ -40,16 +41,7 @@ namespace System 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63 0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95 - 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 255 + 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf // 102 }; private static unsafe bool TryNumberToInt32(ref NumberBuffer number, ref int value) @@ -208,9 +200,10 @@ namespace System internal static int ParseInt32(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { - if (!TryParseInt32(value, styles, info, out int result, out bool failureIsOverflow)) + ParsingStatus status = TryParseInt32(value, styles, info, out int result); + if (status != ParsingStatus.OK) { - ThrowOverflowOrFormatException(failureIsOverflow, nameof(SR.Overflow_Int32)); + ThrowOverflowOrFormatException(status, TypeCode.Int32); } return result; @@ -218,9 +211,10 @@ namespace System internal static long ParseInt64(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { - if (!TryParseInt64(value, styles, info, out long result, out bool failureIsOverflow)) + ParsingStatus status = TryParseInt64(value, styles, info, out long result); + if (status != ParsingStatus.OK) { - ThrowOverflowOrFormatException(failureIsOverflow, nameof(SR.Overflow_Int64)); + ThrowOverflowOrFormatException(status, TypeCode.Int64); } return result; @@ -228,9 +222,10 @@ namespace System internal static uint ParseUInt32(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { - if (!TryParseUInt32(value, styles, info, out uint result, out bool failureIsOverflow)) + ParsingStatus status = TryParseUInt32(value, styles, info, out uint result); + if (status != ParsingStatus.OK) { - ThrowOverflowOrFormatException(failureIsOverflow, nameof(SR.Overflow_UInt32)); + ThrowOverflowOrFormatException(status, TypeCode.UInt32); } return result; @@ -238,9 +233,10 @@ namespace System internal static ulong ParseUInt64(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { - if (!TryParseUInt64(value, styles, info, out ulong result, out bool failureIsOverflow)) + ParsingStatus status = TryParseUInt64(value, styles, info, out ulong result); + if (status != ParsingStatus.OK) { - ThrowOverflowOrFormatException(failureIsOverflow, nameof(SR.Overflow_UInt64)); + ThrowOverflowOrFormatException(status, TypeCode.UInt64); } return result; @@ -476,50 +472,51 @@ namespace System return false; } - internal static unsafe bool TryParseInt32(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out int result, out bool failureIsOverflow) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ParsingStatus TryParseInt32(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out int result) { - result = 0; - failureIsOverflow = false; - if ((styles & ~NumberStyles.Integer) == 0) { // Optimized path for the common case of anything that's allowed for integer style. - return TryParseInt32IntegerStyle(value, styles, info, out result, ref failureIsOverflow); + return TryParseInt32IntegerStyle(value, styles, info, out result); } if ((styles & NumberStyles.AllowHexSpecifier) != 0) { - return TryParseUInt32HexNumberStyle(value, styles, out Unsafe.As(ref result), ref failureIsOverflow); + result = 0; + return TryParseUInt32HexNumberStyle(value, styles, out Unsafe.As(ref result)); } + return TryParseInt32Number(value, styles, info, out result); + } + + private static unsafe ParsingStatus TryParseInt32Number(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out int result) + { + result = 0; byte* pDigits = stackalloc byte[Int32NumberBufferLength]; NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, Int32NumberBufferLength); if (!TryStringToNumber(value, styles, ref number, info)) { - return false; + return ParsingStatus.Failed; } if (!TryNumberToInt32(ref number, ref result)) { - failureIsOverflow = true; - return false; + return ParsingStatus.Overflow; } - return true; + return ParsingStatus.OK; } /// Parses int limited to styles that make up NumberStyles.Integer. - internal static bool TryParseInt32IntegerStyle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out int result, ref bool failureIsOverflow) + internal static ParsingStatus TryParseInt32IntegerStyle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out int result) { Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format"); - Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false"); - if ((uint)value.Length < 1) + if (value.IsEmpty) goto FalseExit; - bool overflow = false; - int sign = 1; int index = 0; int num = value[0]; @@ -537,11 +534,10 @@ namespace System } // Parse leading sign. + int sign = 1; if ((styles & NumberStyles.AllowLeadingSign) != 0) { - string positiveSign = info.PositiveSign, negativeSign = info.NegativeSign; - - if (positiveSign == "+" && negativeSign == "-") + if (info.HasInvariantNumberSigns) { if (num == '-') { @@ -563,6 +559,7 @@ namespace System { value = value.Slice(index); index = 0; + string positiveSign = info.PositiveSign, negativeSign = info.NegativeSign; if (!string.IsNullOrEmpty(positiveSign) && value.StartsWith(positiveSign)) { index += positiveSign.Length; @@ -581,6 +578,7 @@ namespace System } } + bool overflow = false; int answer = 0; if (IsDigit(num)) @@ -592,7 +590,7 @@ namespace System { index++; if ((uint)index >= (uint)value.Length) - goto DoneAtEndButPotentialOverflow; + goto DoneAtEnd; num = value[index]; } while (num == '0'); if (!IsDigit(num)) @@ -605,7 +603,7 @@ namespace System for (int i = 0; i < 8; i++) // next 8 digits can't overflow { if ((uint)index >= (uint)value.Length) - goto DoneAtEndButPotentialOverflow; + goto DoneAtEnd; num = value[index]; if (!IsDigit(num)) goto HasTrailingChars; @@ -613,22 +611,16 @@ namespace System answer = 10 * answer + num - '0'; } - // Potential overflow now processing the 10th digit. if ((uint)index >= (uint)value.Length) - goto DoneAtEndButPotentialOverflow; + goto DoneAtEnd; num = value[index]; if (!IsDigit(num)) goto HasTrailingChars; index++; - if (answer > int.MaxValue / 10) - { - overflow = true; - } + // Potential overflow now processing the 10th digit. + overflow = answer > int.MaxValue / 10; answer = answer * 10 + num - '0'; - if ((uint)answer > (uint)int.MaxValue + (-1 * sign + 1) / 2) - { - overflow = true; - } + overflow |= (uint)answer > int.MaxValue + (((uint)sign) >> 31); if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; @@ -640,24 +632,32 @@ namespace System overflow = true; index++; if ((uint)index >= (uint)value.Length) - goto DoneAtEndButPotentialOverflow; + goto OverflowExit; num = value[index]; } goto HasTrailingChars; } - - FalseExit: // parsing failed - result = 0; - return false; + goto FalseExit; DoneAtEndButPotentialOverflow: if (overflow) { - failureIsOverflow = true; - goto FalseExit; + goto OverflowExit; } + DoneAtEnd: result = answer * sign; - return true; + ParsingStatus status = ParsingStatus.OK; + Exit: + return status; + + FalseExit: // parsing failed + result = 0; + status = ParsingStatus.Failed; + goto Exit; + OverflowExit: + result = 0; + 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. @@ -681,17 +681,13 @@ namespace System } /// Parses long inputs limited to styles that make up NumberStyles.Integer. - internal static bool TryParseInt64IntegerStyle( - ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out long result, ref bool failureIsOverflow) + internal static ParsingStatus TryParseInt64IntegerStyle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out long result) { Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format"); - Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false"); - if ((uint)value.Length < 1) + if (value.IsEmpty) goto FalseExit; - bool overflow = false; - int sign = 1; int index = 0; int num = value[0]; @@ -709,11 +705,10 @@ namespace System } // Parse leading sign. + int sign = 1; if ((styles & NumberStyles.AllowLeadingSign) != 0) { - string positiveSign = info.PositiveSign, negativeSign = info.NegativeSign; - - if (positiveSign == "+" && negativeSign == "-") + if (info.HasInvariantNumberSigns) { if (num == '-') { @@ -735,6 +730,7 @@ namespace System { value = value.Slice(index); index = 0; + string positiveSign = info.PositiveSign, negativeSign = info.NegativeSign; if (!string.IsNullOrEmpty(positiveSign) && value.StartsWith(positiveSign)) { index += positiveSign.Length; @@ -753,6 +749,7 @@ namespace System } } + bool overflow = false; long answer = 0; if (IsDigit(num)) @@ -764,7 +761,7 @@ namespace System { index++; if ((uint)index >= (uint)value.Length) - goto DoneAtEndButPotentialOverflow; + goto DoneAtEnd; num = value[index]; } while (num == '0'); if (!IsDigit(num)) @@ -777,7 +774,7 @@ namespace System for (int i = 0; i < 17; i++) // next 17 digits can't overflow { if ((uint)index >= (uint)value.Length) - goto DoneAtEndButPotentialOverflow; + goto DoneAtEnd; num = value[index]; if (!IsDigit(num)) goto HasTrailingChars; @@ -785,22 +782,16 @@ namespace System answer = 10 * answer + num - '0'; } - // Potential overflow now processing the 19th digit. if ((uint)index >= (uint)value.Length) - goto DoneAtEndButPotentialOverflow; + goto DoneAtEnd; num = value[index]; if (!IsDigit(num)) goto HasTrailingChars; index++; - if (answer > long.MaxValue / 10) - { - overflow = true; - } + // Potential overflow now processing the 19th digit. + overflow = answer > long.MaxValue / 10; answer = answer * 10 + num - '0'; - if ((ulong)answer > (ulong)long.MaxValue + (ulong)((-1 * sign + 1) / 2)) // + sign => 0, - sign => 1 - { - overflow = true; - } + overflow |= (ulong)answer > (ulong)long.MaxValue + (((uint)sign) >> 31); if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; @@ -812,24 +803,32 @@ namespace System overflow = true; index++; if ((uint)index >= (uint)value.Length) - goto DoneAtEndButPotentialOverflow; + goto OverflowExit; num = value[index]; } goto HasTrailingChars; } - - FalseExit: // parsing failed - result = 0; - return false; + goto FalseExit; DoneAtEndButPotentialOverflow: if (overflow) { - failureIsOverflow = true; - goto FalseExit; + goto OverflowExit; } + DoneAtEnd: result = answer * sign; - return true; + ParsingStatus status = ParsingStatus.OK; + Exit: + return status; + + FalseExit: // parsing failed + result = 0; + status = ParsingStatus.Failed; + goto Exit; + OverflowExit: + result = 0; + 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. @@ -852,84 +851,87 @@ namespace System goto DoneAtEndButPotentialOverflow; } - internal static unsafe bool TryParseInt64(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out long result, out bool failureIsOverflow) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ParsingStatus TryParseInt64(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out long result) { - result = 0; - failureIsOverflow = false; - if ((styles & ~NumberStyles.Integer) == 0) { // Optimized path for the common case of anything that's allowed for integer style. - return TryParseInt64IntegerStyle(value, styles, info, out result, ref failureIsOverflow); + return TryParseInt64IntegerStyle(value, styles, info, out result); } if ((styles & NumberStyles.AllowHexSpecifier) != 0) { - return TryParseUInt64HexNumberStyle(value, styles, out Unsafe.As(ref result), ref failureIsOverflow); + result = 0; + return TryParseUInt64HexNumberStyle(value, styles, out Unsafe.As(ref result)); } + return TryParseInt64Number(value, styles, info, out result); + } + + private static unsafe ParsingStatus TryParseInt64Number(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out long result) + { + result = 0; byte* pDigits = stackalloc byte[Int64NumberBufferLength]; NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, Int64NumberBufferLength); if (!TryStringToNumber(value, styles, ref number, info)) { - return false; + return ParsingStatus.Failed; } if (!TryNumberToInt64(ref number, ref result)) { - failureIsOverflow = true; - return false; + return ParsingStatus.Overflow; } - return true; + return ParsingStatus.OK; } - internal static unsafe bool TryParseUInt32(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out uint result, out bool failureIsOverflow) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ParsingStatus TryParseUInt32(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out uint result) { - result = 0; - failureIsOverflow = false; - if ((styles & ~NumberStyles.Integer) == 0) { // Optimized path for the common case of anything that's allowed for integer style. - return TryParseUInt32IntegerStyle(value, styles, info, out result, ref failureIsOverflow); + return TryParseUInt32IntegerStyle(value, styles, info, out result); } if ((styles & NumberStyles.AllowHexSpecifier) != 0) { - return TryParseUInt32HexNumberStyle(value, styles, out result, ref failureIsOverflow); + return TryParseUInt32HexNumberStyle(value, styles, out result); } + return TryParseUInt32Number(value, styles, info, out result); + } + + private static unsafe ParsingStatus TryParseUInt32Number(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out uint result) + { + result = 0; byte* pDigits = stackalloc byte[UInt32NumberBufferLength]; NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, UInt32NumberBufferLength); if (!TryStringToNumber(value, styles, ref number, info)) { - return false; + return ParsingStatus.Failed; } if (!TryNumberToUInt32(ref number, ref result)) { - failureIsOverflow = true; - return false; + return ParsingStatus.Overflow; } - return true; + return ParsingStatus.OK; } /// Parses uint limited to styles that make up NumberStyles.Integer. - internal static bool TryParseUInt32IntegerStyle( - ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out uint result, ref bool failureIsOverflow) + internal static ParsingStatus TryParseUInt32IntegerStyle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out uint result) { Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format"); - Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false"); - if ((uint)value.Length < 1) + if (value.IsEmpty) goto FalseExit; - bool overflow = false; - bool hasNegativeSign = false; int index = 0; int num = value[0]; @@ -947,11 +949,10 @@ namespace System } // Parse leading sign. + bool overflow = false; if ((styles & NumberStyles.AllowLeadingSign) != 0) { - string positiveSign = info.PositiveSign, negativeSign = info.NegativeSign; - - if (positiveSign == "+" && negativeSign == "-") + if (info.HasInvariantNumberSigns) { if (num == '+') { @@ -962,7 +963,7 @@ namespace System } else if (num == '-') { - hasNegativeSign = true; + overflow = true; index++; if ((uint)index >= (uint)value.Length) goto FalseExit; @@ -973,6 +974,7 @@ namespace System { value = value.Slice(index); index = 0; + string positiveSign = info.PositiveSign, negativeSign = info.NegativeSign; if (!string.IsNullOrEmpty(positiveSign) && value.StartsWith(positiveSign)) { index += positiveSign.Length; @@ -982,7 +984,7 @@ namespace System } else if (!string.IsNullOrEmpty(negativeSign) && value.StartsWith(negativeSign)) { - hasNegativeSign = true; + overflow = true; index += negativeSign.Length; if ((uint)index >= (uint)value.Length) goto FalseExit; @@ -1002,11 +1004,11 @@ namespace System { index++; if ((uint)index >= (uint)value.Length) - goto DoneAtEndButPotentialOverflow; + goto DoneAtEnd; num = value[index]; } while (num == '0'); if (!IsDigit(num)) - goto HasTrailingChars; + goto HasTrailingCharsZero; } // Parse most digits, up to the potential for overflow, which can't happen until after 9 digits. @@ -1023,17 +1025,14 @@ namespace System answer = 10 * answer + num - '0'; } - // Potential overflow now processing the 10th digit. if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; num = value[index]; if (!IsDigit(num)) goto HasTrailingChars; index++; - if ((uint)answer > uint.MaxValue / 10 || ((uint)answer == uint.MaxValue / 10 && num > '5')) - { - overflow = true; - } + // Potential overflow now processing the 10th digit. + overflow |= (uint)answer > uint.MaxValue / 10 || ((uint)answer == uint.MaxValue / 10 && num > '5'); answer = answer * 10 + num - '0'; if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; @@ -1046,25 +1045,35 @@ namespace System overflow = true; index++; if ((uint)index >= (uint)value.Length) - goto DoneAtEndButPotentialOverflow; + goto OverflowExit; num = value[index]; } goto HasTrailingChars; } - - FalseExit: // parsing failed - result = 0; - return false; + goto FalseExit; DoneAtEndButPotentialOverflow: - if (overflow || (hasNegativeSign && answer != 0)) + if (overflow) { - failureIsOverflow = true; - goto FalseExit; + goto OverflowExit; } + DoneAtEnd: result = (uint)answer; - return true; + ParsingStatus status = ParsingStatus.OK; + Exit: + return status; + + FalseExit: // parsing failed + result = 0; + status = ParsingStatus.Failed; + goto Exit; + OverflowExit: + result = 0; + status = ParsingStatus.Overflow; + goto Exit; + HasTrailingCharsZero: + overflow = false; 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)) @@ -1087,19 +1096,16 @@ namespace System } /// Parses uint limited to styles that make up NumberStyles.HexNumber. - private static bool TryParseUInt32HexNumberStyle( - ReadOnlySpan value, NumberStyles styles, out uint result, ref bool failureIsOverflow) + private static ParsingStatus TryParseUInt32HexNumberStyle(ReadOnlySpan value, NumberStyles styles, out uint result) { Debug.Assert((styles & ~NumberStyles.HexNumber) == 0, "Only handles subsets of HexNumber format"); - Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false"); - if ((uint)value.Length < 1) + if (value.IsEmpty) goto FalseExit; - bool overflow = false; int index = 0; int num = value[0]; - int numValue = 0; + uint numValue; // Skip past any whitespace at the beginning. if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num)) @@ -1114,8 +1120,9 @@ namespace System while (IsWhite(num)); } - int answer = 0; - int[] charToHexLookup = s_charToHexLookup; + bool overflow = false; + uint answer = 0; + ReadOnlySpan charToHexLookup = CharToHexLookup; if ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF) { @@ -1153,37 +1160,40 @@ namespace System num = value[index]; if ((uint)num >= (uint)charToHexLookup.Length || (numValue = charToHexLookup[num]) == 0xFF) goto HasTrailingChars; - index++; - overflow = true; - 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. Read through any remaining digits. - num = value[index]; - while ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF) + do { index++; if ((uint)index >= (uint)value.Length) - goto DoneAtEndButPotentialOverflow; + goto OverflowExit; num = value[index]; - } + } while ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF); + overflow = true; goto HasTrailingChars; } - - FalseExit: // parsing failed - result = 0; - return false; + goto FalseExit; DoneAtEndButPotentialOverflow: if (overflow) { - failureIsOverflow = true; - goto FalseExit; + goto OverflowExit; } DoneAtEnd: - result = (uint)answer; - return true; + result = answer; + ParsingStatus status = ParsingStatus.OK; + Exit: + return status; + + FalseExit: // parsing failed + result = 0; + status = ParsingStatus.Failed; + goto Exit; + OverflowExit: + result = 0; + 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. @@ -1206,51 +1216,50 @@ namespace System goto DoneAtEndButPotentialOverflow; } - internal static unsafe bool TryParseUInt64(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out ulong result, out bool failureIsOverflow) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ParsingStatus TryParseUInt64(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out ulong result) { - result = 0; - failureIsOverflow = false; - if ((styles & ~NumberStyles.Integer) == 0) { // Optimized path for the common case of anything that's allowed for integer style. - return TryParseUInt64IntegerStyle(value, styles, info, out result, ref failureIsOverflow); + return TryParseUInt64IntegerStyle(value, styles, info, out result); } if ((styles & NumberStyles.AllowHexSpecifier) != 0) { - return TryParseUInt64HexNumberStyle(value, styles, out result, ref failureIsOverflow); + return TryParseUInt64HexNumberStyle(value, styles, out result); } + return TryParseUInt64Number(value, styles, info, out result); + } + + private static unsafe ParsingStatus TryParseUInt64Number(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out ulong result) + { + result = 0; byte* pDigits = stackalloc byte[UInt64NumberBufferLength]; NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, UInt64NumberBufferLength); if (!TryStringToNumber(value, styles, ref number, info)) { - return false; + return ParsingStatus.Failed; } if (!TryNumberToUInt64(ref number, ref result)) { - failureIsOverflow = true; - return false; + return ParsingStatus.Overflow; } - return true; + return ParsingStatus.OK; } /// Parses ulong limited to styles that make up NumberStyles.Integer. - internal static bool TryParseUInt64IntegerStyle( - ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out ulong result, ref bool failureIsOverflow) + internal static ParsingStatus TryParseUInt64IntegerStyle(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out ulong result) { Debug.Assert((styles & ~NumberStyles.Integer) == 0, "Only handles subsets of Integer format"); - Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false"); - if ((uint)value.Length < 1) + if (value.IsEmpty) goto FalseExit; - bool overflow = false; - bool hasNegativeSign = false; int index = 0; int num = value[0]; @@ -1268,11 +1277,10 @@ namespace System } // Parse leading sign. + bool overflow = false; if ((styles & NumberStyles.AllowLeadingSign) != 0) { - string positiveSign = info.PositiveSign, negativeSign = info.NegativeSign; - - if (positiveSign == "+" && negativeSign == "-") + if (info.HasInvariantNumberSigns) { if (num == '+') { @@ -1283,7 +1291,7 @@ namespace System } else if (num == '-') { - hasNegativeSign = true; + overflow = true; index++; if ((uint)index >= (uint)value.Length) goto FalseExit; @@ -1294,6 +1302,7 @@ namespace System { value = value.Slice(index); index = 0; + string positiveSign = info.PositiveSign, negativeSign = info.NegativeSign; if (!string.IsNullOrEmpty(positiveSign) && value.StartsWith(positiveSign)) { index += positiveSign.Length; @@ -1303,7 +1312,7 @@ namespace System } else if (!string.IsNullOrEmpty(negativeSign) && value.StartsWith(negativeSign)) { - hasNegativeSign = true; + overflow = true; index += negativeSign.Length; if ((uint)index >= (uint)value.Length) goto FalseExit; @@ -1323,11 +1332,11 @@ namespace System { index++; if ((uint)index >= (uint)value.Length) - goto DoneAtEndButPotentialOverflow; + goto DoneAtEnd; num = value[index]; } while (num == '0'); if (!IsDigit(num)) - goto HasTrailingChars; + goto HasTrailingCharsZero; } // Parse most digits, up to the potential for overflow, which can't happen until after 19 digits. @@ -1344,17 +1353,14 @@ namespace System answer = 10 * answer + num - '0'; } - // Potential overflow now processing the 20th digit. if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; num = value[index]; if (!IsDigit(num)) goto HasTrailingChars; index++; - if ((ulong)answer > ulong.MaxValue / 10 || ((ulong)answer == ulong.MaxValue / 10 && num > '5')) - { - overflow = true; - } + // Potential overflow now processing the 20th digit. + overflow |= (ulong)answer > ulong.MaxValue / 10 || ((ulong)answer == ulong.MaxValue / 10 && num > '5'); answer = answer * 10 + num - '0'; if ((uint)index >= (uint)value.Length) goto DoneAtEndButPotentialOverflow; @@ -1367,25 +1373,35 @@ namespace System overflow = true; index++; if ((uint)index >= (uint)value.Length) - goto DoneAtEndButPotentialOverflow; + goto OverflowExit; num = value[index]; } goto HasTrailingChars; } - - FalseExit: // parsing failed - result = 0; - return false; + goto FalseExit; DoneAtEndButPotentialOverflow: - if (overflow || (hasNegativeSign && answer != 0)) + if (overflow) { - failureIsOverflow = true; - goto FalseExit; + goto OverflowExit; } + DoneAtEnd: result = (ulong)answer; - return true; + ParsingStatus status = ParsingStatus.OK; + Exit: + return status; + FalseExit: // parsing failed + result = 0; + status = ParsingStatus.Failed; + goto Exit; + OverflowExit: + result = 0; + status = ParsingStatus.Overflow; + goto Exit; + + HasTrailingCharsZero: + overflow = false; 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)) @@ -1408,19 +1424,16 @@ namespace System } /// Parses ulong limited to styles that make up NumberStyles.HexNumber. - private static bool TryParseUInt64HexNumberStyle( - ReadOnlySpan value, NumberStyles styles, out ulong result, ref bool failureIsOverflow) + private static ParsingStatus TryParseUInt64HexNumberStyle(ReadOnlySpan value, NumberStyles styles, out ulong result) { Debug.Assert((styles & ~NumberStyles.HexNumber) == 0, "Only handles subsets of HexNumber format"); - Debug.Assert(!failureIsOverflow, $"failureIsOverflow should have been initialized to false"); - if ((uint)value.Length < 1) + if (value.IsEmpty) goto FalseExit; - bool overflow = false; int index = 0; int num = value[0]; - int numValue = 0; + uint numValue; // Skip past any whitespace at the beginning. if ((styles & NumberStyles.AllowLeadingWhite) != 0 && IsWhite(num)) @@ -1435,8 +1448,9 @@ namespace System while (IsWhite(num)); } - long answer = 0; - int[] charToHexLookup = s_charToHexLookup; + bool overflow = false; + ulong answer = 0; + ReadOnlySpan charToHexLookup = CharToHexLookup; if ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF) { @@ -1474,37 +1488,40 @@ namespace System num = value[index]; if ((uint)num >= (uint)charToHexLookup.Length || (numValue = charToHexLookup[num]) == 0xFF) goto HasTrailingChars; - index++; - overflow = true; - 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. Read through any remaining digits. - num = value[index]; - while ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF) + do { index++; if ((uint)index >= (uint)value.Length) - goto DoneAtEndButPotentialOverflow; + goto OverflowExit; num = value[index]; - } + } while ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF); + overflow = true; goto HasTrailingChars; } - - FalseExit: // parsing failed - result = 0; - return false; + goto FalseExit; DoneAtEndButPotentialOverflow: if (overflow) { - failureIsOverflow = true; - goto FalseExit; + goto OverflowExit; } DoneAtEnd: - result = (ulong)answer; - return true; + result = answer; + ParsingStatus status = ParsingStatus.OK; + Exit: + return status; + + FalseExit: // parsing failed + result = 0; + status = ParsingStatus.Failed; + goto Exit; + OverflowExit: + result = 0; + 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. @@ -1529,9 +1546,10 @@ namespace System internal static decimal ParseDecimal(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) { - if (!TryParseDecimal(value, styles, info, out decimal result, out bool failureIsOverflow)) + ParsingStatus status = TryParseDecimal(value, styles, info, out decimal result); + if (status != ParsingStatus.OK) { - ThrowOverflowOrFormatException(failureIsOverflow, nameof(SR.Overflow_Decimal)); + ThrowOverflowOrFormatException(status, TypeCode.Decimal); } return result; @@ -1646,7 +1664,7 @@ namespace System { if (!TryParseDouble(value, styles, info, out double result)) { - ThrowOverflowOrFormatException(overflow: false, overflowResourceKey: null); + ThrowOverflowOrFormatException(ParsingStatus.Failed); } return result; @@ -1656,32 +1674,30 @@ namespace System { if (!TryParseSingle(value, styles, info, out float result)) { - ThrowOverflowOrFormatException(overflow: false, overflowResourceKey: null); + ThrowOverflowOrFormatException(ParsingStatus.Failed); } return result; } - internal static unsafe bool TryParseDecimal(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out decimal result, out bool failureIsOverflow) + internal static unsafe ParsingStatus TryParseDecimal(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out decimal result) { byte* pDigits = stackalloc byte[DecimalNumberBufferLength]; NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, pDigits, DecimalNumberBufferLength); result = 0; - failureIsOverflow = false; if (!TryStringToNumber(value, styles, ref number, info)) { - return false; + return ParsingStatus.Failed; } if (!TryNumberToDecimal(ref number, ref result)) { - failureIsOverflow = true; - return false; + return ParsingStatus.Overflow; } - return true; + return ParsingStatus.OK; } internal static unsafe bool TryParseDouble(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out double result) @@ -1813,14 +1829,6 @@ namespace System return true; } - private static unsafe void StringToNumber(ReadOnlySpan value, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info) - { - if (!TryStringToNumber(value, styles, ref number, info)) - { - ThrowOverflowOrFormatException(overflow: false, overflowResourceKey: null); - } - } - internal static unsafe bool TryStringToNumber(ReadOnlySpan value, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info) { Debug.Assert(info != null); @@ -1828,7 +1836,7 @@ namespace System { char* p = stringPointer; if (!TryParseNumber(ref p, p + value.Length, styles, ref number, info) - || (p - stringPointer < value.Length && !TrailingZeros(value, (int)(p - stringPointer)))) + || ((int)(p - stringPointer) < value.Length && !TrailingZeros(value, (int)(p - stringPointer)))) { number.CheckConsistency(); return false; @@ -1842,7 +1850,7 @@ namespace System private static bool TrailingZeros(ReadOnlySpan value, int index) { // For compatibility, we need to allow trailing zeros at the end of a number string - for (int i = index; i < value.Length; i++) + for (int i = index; (uint)i < (uint)value.Length; i++) { if (value[i] != '\0') { @@ -1882,15 +1890,60 @@ namespace System return null; } - private static bool IsWhite(int ch) => ch == 0x20 || ((uint)(ch - 0x09) <= (0x0D - 0x09)); + // Ternary op is a workaround for https://github.com/dotnet/coreclr/issues/914 + private static bool IsWhite(int ch) => ch == 0x20 || (uint)(ch - 0x09) <= (0x0D - 0x09) ? true : false; private static bool IsDigit(int ch) => ((uint)ch - '0') <= 9; - private static void ThrowOverflowOrFormatException(bool overflow, string overflowResourceKey) + internal enum ParsingStatus + { + OK, + Failed, + Overflow + } + + internal static void ThrowOverflowOrFormatException(ParsingStatus status, TypeCode type = 0) => throw GetException(status, type); + + internal static void ThrowOverflowException(TypeCode type) => throw GetException(ParsingStatus.Overflow, type); + + private static Exception GetException(ParsingStatus status, TypeCode type) { - throw overflow ? - new OverflowException(SR.GetResourceString(overflowResourceKey)) : - (Exception)new FormatException(SR.Format_InvalidString); + if (status == ParsingStatus.Failed) + return new FormatException(SR.Format_InvalidString); + + 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); } internal static double NumberToDouble(ref NumberBuffer number) diff --git a/src/libraries/System.Private.CoreLib/src/System/ParseNumbers.cs b/src/libraries/System.Private.CoreLib/src/System/ParseNumbers.cs index dab4cb2..0978186 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ParseNumbers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ParseNumbers.cs @@ -105,7 +105,7 @@ namespace System // Return the value properly signed. if ((ulong)result == 0x8000000000000000 && sign == 1 && r == 10 && ((flags & TreatAsUnsigned) == 0)) - throw new OverflowException(SR.Overflow_Int64); + Number.ThrowOverflowException(TypeCode.Int64); if (r == 10) { @@ -196,16 +196,16 @@ namespace System if ((flags & TreatAsI1) != 0) { if ((uint)result > 0xFF) - throw new OverflowException(SR.Overflow_SByte); + Number.ThrowOverflowException(TypeCode.SByte); } else if ((flags & TreatAsI2) != 0) { if ((uint)result > 0xFFFF) - throw new OverflowException(SR.Overflow_Int16); + Number.ThrowOverflowException(TypeCode.Int16); } else if ((uint)result == 0x80000000 && sign == 1 && r == 10 && ((flags & TreatAsUnsigned) == 0)) { - throw new OverflowException(SR.Overflow_Int32); + Number.ThrowOverflowException(TypeCode.Int32); } if (r == 10) @@ -527,7 +527,7 @@ namespace System // Check for overflows - this is sufficient & correct. if (result > maxVal || ((long)result) < 0) { - ThrowOverflowInt64Exception(); + Number.ThrowOverflowException(TypeCode.Int64); } result = result * (ulong)radix + (ulong)value; @@ -536,7 +536,7 @@ namespace System if ((long)result < 0 && result != 0x8000000000000000) { - ThrowOverflowInt64Exception(); + Number.ThrowOverflowException(TypeCode.Int64); } } else @@ -554,14 +554,14 @@ namespace System // Check for overflows - this is sufficient & correct. if (result > maxVal) { - ThrowOverflowUInt64Exception(); + Number.ThrowOverflowException(TypeCode.UInt64); } ulong temp = result * (ulong)radix + (ulong)value; if (temp < result) // this means overflow as well { - ThrowOverflowUInt64Exception(); + Number.ThrowOverflowException(TypeCode.UInt64); } result = temp; @@ -588,14 +588,14 @@ namespace System // Check for overflows - this is sufficient & correct. if (result > maxVal || (int)result < 0) { - ThrowOverflowInt32Exception(); + Number.ThrowOverflowException(TypeCode.Int32); } result = result * (uint)radix + (uint)value; i++; } if ((int)result < 0 && result != 0x80000000) { - ThrowOverflowInt32Exception(); + Number.ThrowOverflowException(TypeCode.Int32); } } else @@ -613,14 +613,14 @@ namespace System // Check for overflows - this is sufficient & correct. if (result > maxVal) { - throw new OverflowException(SR.Overflow_UInt32); + Number.ThrowOverflowException(TypeCode.UInt32); } uint temp = result * (uint)radix + (uint)value; if (temp < result) // this means overflow as well { - ThrowOverflowUInt32Exception(); + Number.ThrowOverflowException(TypeCode.UInt32); } result = temp; @@ -631,11 +631,6 @@ namespace System return (int)result; } - private static void ThrowOverflowInt32Exception() => throw new OverflowException(SR.Overflow_Int32); - private static void ThrowOverflowInt64Exception() => throw new OverflowException(SR.Overflow_Int64); - private static void ThrowOverflowUInt32Exception() => throw new OverflowException(SR.Overflow_UInt32); - private static void ThrowOverflowUInt64Exception() => throw new OverflowException(SR.Overflow_UInt64); - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsDigit(char c, int radix, out int result) { diff --git a/src/libraries/System.Private.CoreLib/src/System/SByte.cs b/src/libraries/System.Private.CoreLib/src/System/SByte.cs index d311484..c3bc386 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SByte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SByte.cs @@ -155,26 +155,18 @@ namespace System private static sbyte Parse(ReadOnlySpan s, NumberStyles style, NumberFormatInfo info) { - int i = 0; - try + Number.ParsingStatus status = Number.TryParseInt32(s, style, info, out int i); + if (status != Number.ParsingStatus.OK) { - i = Number.ParseInt32(s, style, info); - } - catch (OverflowException e) - { - throw new OverflowException(SR.Overflow_SByte, e); + Number.ThrowOverflowOrFormatException(status, TypeCode.SByte); } - if ((style & NumberStyles.AllowHexSpecifier) != 0) - { // We are parsing a hexadecimal number - if ((i < 0) || i > byte.MaxValue) - { - throw new OverflowException(SR.Overflow_SByte); - } - return (sbyte)i; + // For hex number styles AllowHexSpecifier >> 2 == 0x80 and cancels out MinValue so the check is effectively: (uint)i > byte.MaxValue + // For integer styles it's zero and the effective check is (uint)(i - MinValue) > byte.MaxValue + if ((uint)(i - MinValue - ((int)(style & NumberStyles.AllowHexSpecifier) >> 2)) > byte.MaxValue) + { + Number.ThrowOverflowException(TypeCode.SByte); } - - if (i < MinValue || i > MaxValue) throw new OverflowException(SR.Overflow_SByte); return (sbyte)i; } @@ -219,25 +211,12 @@ namespace System private static bool TryParse(ReadOnlySpan s, NumberStyles style, NumberFormatInfo info, out sbyte result) { - result = 0; - int i; - if (!Number.TryParseInt32(s, style, info, out i, out _)) - { - return false; - } - - if ((style & NumberStyles.AllowHexSpecifier) != 0) - { // We are parsing a hexadecimal number - if ((i < 0) || i > byte.MaxValue) - { - return false; - } - result = (sbyte)i; - return true; - } - - if (i < MinValue || i > MaxValue) + // For hex number styles AllowHexSpecifier >> 2 == 0x80 and cancels out MinValue so the check is effectively: (uint)i > byte.MaxValue + // For integer styles it's zero and the effective check is (uint)(i - MinValue) > byte.MaxValue + if (Number.TryParseInt32(s, style, info, out int i) != Number.ParsingStatus.OK + || (uint)(i - MinValue - ((int)(style & NumberStyles.AllowHexSpecifier) >> 2)) > byte.MaxValue) { + result = 0; return false; } result = (sbyte)i; diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt16.cs b/src/libraries/System.Private.CoreLib/src/System/UInt16.cs index 38c8b97..0cce408 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt16.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt16.cs @@ -133,17 +133,13 @@ namespace System private static ushort Parse(ReadOnlySpan s, NumberStyles style, NumberFormatInfo info) { - uint i = 0; - try + Number.ParsingStatus status = Number.TryParseUInt32(s, style, info, out uint i); + if (status != Number.ParsingStatus.OK) { - i = Number.ParseUInt32(s, style, info); - } - catch (OverflowException e) - { - throw new OverflowException(SR.Overflow_UInt16, e); + Number.ThrowOverflowOrFormatException(status, TypeCode.UInt16); } - if (i > MaxValue) throw new OverflowException(SR.Overflow_UInt16); + if (i > MaxValue) Number.ThrowOverflowException(TypeCode.UInt16); return (ushort)i; } @@ -188,14 +184,10 @@ namespace System private static bool TryParse(ReadOnlySpan s, NumberStyles style, NumberFormatInfo info, out ushort result) { - result = 0; - uint i; - if (!Number.TryParseUInt32(s, style, info, out i, out _)) - { - return false; - } - if (i > MaxValue) + if (Number.TryParseUInt32(s, style, info, out uint i) != Number.ParsingStatus.OK + || i > MaxValue) { + result = 0; return false; } result = (ushort)i; diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt32.cs b/src/libraries/System.Private.CoreLib/src/System/UInt32.cs index 576b55f..19faee1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt32.cs @@ -148,13 +148,13 @@ namespace System return false; } - return Number.TryParseUInt32(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result, out _); + return Number.TryParseUInt32IntegerStyle(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result) == Number.ParsingStatus.OK; } [CLSCompliant(false)] public static bool TryParse(ReadOnlySpan s, out uint result) { - return Number.TryParseUInt32(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result, out _); + return Number.TryParseUInt32IntegerStyle(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result) == Number.ParsingStatus.OK; } [CLSCompliant(false)] @@ -168,14 +168,14 @@ namespace System return false; } - return Number.TryParseUInt32(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); + return Number.TryParseUInt32(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; } [CLSCompliant(false)] public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider provider, out uint result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseUInt32(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); + return Number.TryParseUInt32(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; } // diff --git a/src/libraries/System.Private.CoreLib/src/System/UInt64.cs b/src/libraries/System.Private.CoreLib/src/System/UInt64.cs index 0abe8d4..39fa643 100644 --- a/src/libraries/System.Private.CoreLib/src/System/UInt64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/UInt64.cs @@ -145,13 +145,13 @@ namespace System return false; } - return Number.TryParseUInt64(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result, out _); + return Number.TryParseUInt64IntegerStyle(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result) == Number.ParsingStatus.OK; } [CLSCompliant(false)] public static bool TryParse(ReadOnlySpan s, out ulong result) { - return Number.TryParseUInt64(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result, out _); + return Number.TryParseUInt64IntegerStyle(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result) == Number.ParsingStatus.OK; } [CLSCompliant(false)] @@ -165,14 +165,14 @@ namespace System return false; } - return Number.TryParseUInt64(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); + return Number.TryParseUInt64(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; } [CLSCompliant(false)] public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider provider, out ulong result) { NumberFormatInfo.ValidateParseStyleInteger(style); - return Number.TryParseUInt64(s, style, NumberFormatInfo.GetInstance(provider), out result, out _); + return Number.TryParseUInt64(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; } // -- 2.7.4