diff --git a/PreciseNumber.Test/PreciseNumberParseCultureTests.cs b/PreciseNumber.Test/PreciseNumberParseCultureTests.cs index 59108e9..1ad4117 100644 --- a/PreciseNumber.Test/PreciseNumberParseCultureTests.cs +++ b/PreciseNumber.Test/PreciseNumberParseCultureTests.cs @@ -3,6 +3,7 @@ namespace ktsu.PreciseNumber.Test; using System.Globalization; +using System.Numerics; [TestClass] public class PreciseNumberParseCultureTests @@ -135,6 +136,27 @@ public void Parse_ThrowsForWhatTheStyleDoesNotAllow() Assert.ThrowsExactly(() => PreciseNumber.Parse("1e 5", NumberStyles.Float, CultureInfo.InvariantCulture)); } + [TestMethod] + [DataRow("10", NumberStyles.HexNumber)] + [DataRow("A", NumberStyles.HexNumber)] + [DataRow("10", NumberStyles.AllowHexSpecifier)] + [DataRow("1010", NumberStyles.BinaryNumber)] + [DataRow("1010", NumberStyles.AllowBinarySpecifier)] + public void ParseAndTryParse_RejectHexAndBinaryStylesAsDecimalDoes(string text, NumberStyles style) + { + Assert.ThrowsExactly(() => decimal.Parse(text, style, CultureInfo.InvariantCulture)); + + Assert.ThrowsExactly(() => PreciseNumber.Parse(text, style, CultureInfo.InvariantCulture)); + Assert.ThrowsExactly(() => PreciseNumber.Parse(text.AsSpan(), style, CultureInfo.InvariantCulture)); + Assert.ThrowsExactly(() => PreciseNumber.TryParse(text, style, CultureInfo.InvariantCulture, out _)); + Assert.ThrowsExactly(() => PreciseNumber.TryParse(text.AsSpan(), style, CultureInfo.InvariantCulture, out _)); + Assert.ThrowsExactly(() => ParseGeneric(text, style)); + } + + private static T ParseGeneric(string text, NumberStyles style) + where T : INumberBase => + T.Parse(text, style, CultureInfo.InvariantCulture); + [TestMethod] public void Parse_WithoutAStyleStillReadsFractionsExponentsAndSigns() { diff --git a/PreciseNumber/PreciseNumber.cs b/PreciseNumber/PreciseNumber.cs index 40355c7..9e9167e 100644 --- a/PreciseNumber/PreciseNumber.cs +++ b/PreciseNumber/PreciseNumber.cs @@ -48,6 +48,14 @@ public readonly partial record struct PreciseNumber /// The message carried by the that parsing throws. /// private const string InvalidFormatMessage = "Input string was not in a correct format."; + private const string UnsupportedParseStylesMessage = "The number styles AllowHexSpecifier and AllowBinarySpecifier are not supported by PreciseNumber."; + +#if NET8_0_OR_GREATER + private const NumberStyles UnsupportedParseStyles = NumberStyles.AllowHexSpecifier | NumberStyles.AllowBinarySpecifier; +#else + // NumberStyles.AllowBinarySpecifier arrived in .NET 8, but a caller on net7.0 can still pass its value. + private const NumberStyles UnsupportedParseStyles = NumberStyles.AllowHexSpecifier | (NumberStyles)0x400; +#endif /// /// The fewest significant digits produces @@ -1203,6 +1211,13 @@ public static PreciseNumber Parse(ReadOnlySpan s, NumberStyles style, IFor // Parse(x.ToString(p), p) round-trips for every provider. NumberFormatInfo numberFormat = NumberFormatInfo.GetInstance(provider ?? InvariantCulture); + // Hex and binary input would be read as decimal digits and come back as the wrong number, + // so refuse those styles outright, as decimal and double do. + if ((style & UnsupportedParseStyles) != 0) + { + throw new ArgumentException(UnsupportedParseStylesMessage, nameof(style)); + } + s = TrimAllowedWhite(s, style); if (s.IsEmpty)