From e125e93c3f42a1b90bcb831756b78fab561ab775 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 5 Jul 2024 17:08:20 -0700 Subject: [PATCH] Fix an issue with complex number parsing (#104388) --- .../src/System/Numerics/Complex.cs | 4 +- .../tests/ComplexTests.cs | 114 ++++++++++++++++++ 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs index 2eb4458ce38fc..51d0580c285ed 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs @@ -2113,7 +2113,7 @@ public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatPro return false; } - if (!double.TryParse(s.Slice(openBracket + 1, semicolon), style, provider, out double real)) + if (!double.TryParse(s.Slice(openBracket + 1, semicolon - openBracket - 1), style, provider, out double real)) { result = default; return false; @@ -2126,7 +2126,7 @@ public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatPro semicolon += 1; } - if (!double.TryParse(s.Slice(semicolon + 1, closeBracket - semicolon), style, provider, out double imaginary)) + if (!double.TryParse(s.Slice(semicolon + 1, closeBracket - semicolon - 1), style, provider, out double imaginary)) { result = default; return false; diff --git a/src/libraries/System.Runtime.Numerics/tests/ComplexTests.cs b/src/libraries/System.Runtime.Numerics/tests/ComplexTests.cs index fc843295b2e51..953a81490813c 100644 --- a/src/libraries/System.Runtime.Numerics/tests/ComplexTests.cs +++ b/src/libraries/System.Runtime.Numerics/tests/ComplexTests.cs @@ -70,6 +70,120 @@ public static void SqrtMinusOne() Assert.Equal(Complex.Sqrt(-1.0), Complex.ImaginaryOne); } + public static IEnumerable Parse_Valid_TestData() + { + NumberStyles defaultStyle = NumberStyles.Float | NumberStyles.AllowThousands; + + NumberFormatInfo emptyFormat = NumberFormatInfo.CurrentInfo; + + var dollarSignCommaSeparatorFormat = new NumberFormatInfo() + { + CurrencySymbol = "$", + CurrencyGroupSeparator = "," + }; + + var decimalSeparatorFormat = new NumberFormatInfo() + { + NumberDecimalSeparator = "." + }; + + NumberFormatInfo invariantFormat = NumberFormatInfo.InvariantInfo; + + yield return new object[] { "-123", defaultStyle, null, -123.0 }; + yield return new object[] { "0", defaultStyle, null, 0.0 }; + yield return new object[] { "123", defaultStyle, null, 123.0 }; + yield return new object[] { " 123 ", defaultStyle, null, 123.0 }; + yield return new object[] { (567.89).ToString(), defaultStyle, null, 567.89 }; + yield return new object[] { (-567.89).ToString(), defaultStyle, null, -567.89 }; + yield return new object[] { "1E23", defaultStyle, null, 1E23 }; + yield return new object[] { "9007199254740997.0", defaultStyle, invariantFormat, 9007199254740996.0 }; + yield return new object[] { "9007199254740997.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", defaultStyle, invariantFormat, 9007199254740996.0 }; + yield return new object[] { "9007199254740997.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", defaultStyle, invariantFormat, 9007199254740996.0 }; + yield return new object[] { "9007199254740997.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", defaultStyle, invariantFormat, 9007199254740998.0 }; + yield return new object[] { "9007199254740997.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", defaultStyle, invariantFormat, 9007199254740998.0 }; + yield return new object[] { "9007199254740997.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", defaultStyle, invariantFormat, 9007199254740998.0 }; + yield return new object[] { "5.005", defaultStyle, invariantFormat, 5.005 }; + yield return new object[] { "5.050", defaultStyle, invariantFormat, 5.05 }; + yield return new object[] { "5.005000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", defaultStyle, invariantFormat, 5.005 }; + yield return new object[] { "5.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005", defaultStyle, invariantFormat, 5.0 }; + yield return new object[] { "5.0050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", defaultStyle, invariantFormat, 5.005 }; + + yield return new object[] { emptyFormat.NumberDecimalSeparator + "234", defaultStyle, null, 0.234 }; + yield return new object[] { "234" + emptyFormat.NumberDecimalSeparator, defaultStyle, null, 234.0 }; + yield return new object[] { new string('0', 458) + "1" + new string('0', 308) + emptyFormat.NumberDecimalSeparator, defaultStyle, null, 1E308 }; + yield return new object[] { new string('0', 459) + "1" + new string('0', 308) + emptyFormat.NumberDecimalSeparator, defaultStyle, null, 1E308 }; + + yield return new object[] { "5005.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", defaultStyle, invariantFormat, 5005.0 }; + yield return new object[] { "50050.0", defaultStyle, invariantFormat, 50050.0 }; + yield return new object[] { "5005", defaultStyle, invariantFormat, 5005.0 }; + yield return new object[] { "050050", defaultStyle, invariantFormat, 50050.0 }; + yield return new object[] { "0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", defaultStyle, invariantFormat, 0.0 }; + yield return new object[] { "0.005", defaultStyle, invariantFormat, 0.005 }; + yield return new object[] { "0.0500", defaultStyle, invariantFormat, 0.05 }; + yield return new object[] { "6250000000000000000000000000000000e-12", defaultStyle, invariantFormat, 6.25e21 }; + yield return new object[] { "6250000e0", defaultStyle, invariantFormat, 6.25e6 }; + yield return new object[] { "6250100e-5", defaultStyle, invariantFormat, 62.501 }; + yield return new object[] { "625010.00e-4", defaultStyle, invariantFormat, 62.501 }; + yield return new object[] { "62500e-4", defaultStyle, invariantFormat, 6.25 }; + yield return new object[] { "62500", defaultStyle, invariantFormat, 62500.0 }; + yield return new object[] { "10e-3", defaultStyle, invariantFormat, 0.01 }; + + yield return new object[] { (123.1).ToString(), NumberStyles.AllowDecimalPoint, null, 123.1 }; + yield return new object[] { (1000.0).ToString("N0"), NumberStyles.AllowThousands, null, 1000.0 }; + + yield return new object[] { "123", NumberStyles.Any, emptyFormat, 123.0 }; + yield return new object[] { (123.567).ToString(), NumberStyles.Any, emptyFormat, 123.567 }; + yield return new object[] { "123", NumberStyles.Float, emptyFormat, 123.0 }; + yield return new object[] { "$1,000", NumberStyles.Currency, dollarSignCommaSeparatorFormat, 1000.0 }; + yield return new object[] { "$1000", NumberStyles.Currency, dollarSignCommaSeparatorFormat, 1000.0 }; + yield return new object[] { "123.123", NumberStyles.Float, decimalSeparatorFormat, 123.123 }; + yield return new object[] { "(123)", NumberStyles.AllowParentheses, decimalSeparatorFormat, -123.0 }; + + yield return new object[] { "NaN", NumberStyles.Any, invariantFormat, double.NaN }; + yield return new object[] { "Infinity", NumberStyles.Any, invariantFormat, double.PositiveInfinity }; + yield return new object[] { "-Infinity", NumberStyles.Any, invariantFormat, double.NegativeInfinity }; + } + + [Theory] + [MemberData(nameof(Parse_Valid_TestData))] + public static void Parse(string valueScalar, NumberStyles style, IFormatProvider provider, double expectedScalar) + { + string value = $"<{valueScalar}; {valueScalar}>"; + Complex expected = new Complex(expectedScalar, expectedScalar); + + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + Complex result; + if ((style & ~(NumberStyles.Float | NumberStyles.AllowThousands)) == 0 && style != NumberStyles.None) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.True(Complex.TryParse(value, null, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Complex.Parse(value, null)); + } + + Assert.Equal(expected, Complex.Parse(value, provider)); + } + + // Use Parse(string, NumberStyles, IFormatProvider) + Assert.True(Complex.TryParse(value, style, provider, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Complex.Parse(value, style, provider)); + + if (isDefaultProvider) + { + // Use Parse(string, NumberStyles) or Parse(string, NumberStyles, IFormatProvider) + Assert.True(Complex.TryParse(value, style, NumberFormatInfo.CurrentInfo, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Complex.Parse(value, style, null)); + Assert.Equal(expected, Complex.Parse(value, style, NumberFormatInfo.CurrentInfo)); + } + } + public static IEnumerable Valid_2_TestData() { foreach (double real in s_validDoubleValues)