From aa4ec834045eb277fd28fb07dc3adbd6c6cbac9f Mon Sep 17 00:00:00 2001 From: Christoph Ruegg Date: Tue, 18 Aug 2009 22:45:09 +0800 Subject: [PATCH] globalization: complex ToString and Parse Signed-off-by: Christoph Ruegg --- src/Numerics/Complex.cs | 204 ++++++++----- src/UnitTests/ComplexTests/ComplexTest.cs | 191 ++---------- .../ComplexTests/ComplexTextHandlingTest.cs | 282 ++++++++++++++++++ src/UnitTests/UnitTests.csproj | 1 + 4 files changed, 445 insertions(+), 233 deletions(-) create mode 100644 src/UnitTests/ComplexTests/ComplexTextHandlingTest.cs diff --git a/src/Numerics/Complex.cs b/src/Numerics/Complex.cs index 26f0c1f0..7627fe15 100644 --- a/src/Numerics/Complex.cs +++ b/src/Numerics/Complex.cs @@ -29,9 +29,9 @@ namespace MathNet.Numerics { using System; + using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; - using System.Text.RegularExpressions; using Properties; /// @@ -73,14 +73,6 @@ namespace MathNet.Numerics { #region fields - /// - /// Regular expression used to parse strings into complex numbers. - /// - private static readonly Regex _parseExpression = - new Regex( - @"^((?(([-+]?(\d+\.?\d*|\d*\.?\d+)([Ee][-+]?[0-9]+)?)|(NaN)|([-+]?Infinity)))|(?(([-+]?((\d+\.?\d*|\d*\.?\d+)([Ee][-+]?[0-9]+)?)|(NaN)|([-+]?Infinity))?[i]))|(?(([-+]?(\d+\.?\d*|\d*\.?\d+)([Ee][-+]?[0-9]+)?)|(NaN)|([-+]?Infinity)))(?(([-+]((\d+\.?\d*|\d*\.?\d+)([Ee][-+]?[0-9]+)?)|[-+](NaN)|([-+]Infinity))?[i])))$", - RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - /// /// Represents imaginary unit number. /// @@ -121,7 +113,7 @@ namespace MathNet.Numerics #region Constructor /// - /// Initializes a new instance of the Complex struct with the given real + /// Initializes a new instance of the Complex structure with the given real /// and imaginary parts. /// /// @@ -149,8 +141,7 @@ namespace MathNet.Numerics /// infinite real and imaginary part. If you need more formal complex /// number handling (according to the Riemann Sphere and the extended /// complex plane C*, or using directed infinity) please check out the - /// alternative MathNet.PreciseNumerics and MathNet.Symbolics packages - /// instead. + /// alternative Math.NET symbolics packages instead. /// /// A value representing the infinity value. public static Complex Infinity @@ -242,10 +233,13 @@ namespace MathNet.Numerics } /// - /// Gets a value indicating whether the provided Complex evaluates to a - /// value that is not a number. + /// Gets a value indicating whether the provided Complexevaluates + /// to a value that is not a number. /// - /// true if this instance is NaN; otherwise, false. + /// + /// true if this instance is ; otherwise, + /// false. + /// public bool IsNaN { get { return double.IsNaN(_real) || double.IsNaN(_imag); } @@ -644,14 +638,16 @@ namespace MathNet.Numerics /// public string ToString(string format, IFormatProvider formatProvider) { + var numberFormatInfo = formatProvider.GetNumberFormatInfo(); + if (IsNaN) { - return "NaN"; + return numberFormatInfo.NaNSymbol; } if (IsInfinity) { - return "Infinity"; + return numberFormatInfo.PositiveInfinitySymbol; } var ret = new StringBuilder(); @@ -678,6 +674,11 @@ namespace MathNet.Numerics ret.Append(_imag.ToString(format, formatProvider)).Append("i"); } + if (ret.Length == 0) + { + ret.Append((0.0).ToString(format, formatProvider)); + } + return ret.ToString(); } @@ -1041,9 +1042,9 @@ namespace MathNet.Numerics #region Parse Functions /// - /// Creates a complex number based on a string. The string can be in the following - /// formats(without the quotes): 'n', 'ni', 'n +/- ni', 'n,n', 'n,ni,' '(n,n)', or - /// '(n,ni)', where n is a real number. + /// Creates a complex number based on a string. The string can be in the + /// following formats (without the quotes): 'n', 'ni', 'n +/- ni', + /// 'ni +/- n', 'n,n', 'n,ni,' '(n,n)', or '(n,ni)', where n is a double. /// /// /// A complex number containing the value specified by the given string. @@ -1057,9 +1058,9 @@ namespace MathNet.Numerics } /// - /// Creates a complex number based on a string. The string can be in the following - /// formats(without the quotes): 'n', 'ni', 'n +/- ni', 'n,n', 'n,ni,' '(n,n)', or - /// '(n,ni)', where n is a double. + /// Creates a complex number based on a string. The string can be in the + /// following formats (without the quotes): 'n', 'ni', 'n +/- ni', + /// 'ni +/- n', 'n,n', 'n,ni,' '(n,n)', or '(n,ni)', where n is a double. /// /// /// A complex number containing the value specified by the given string. @@ -1068,7 +1069,8 @@ namespace MathNet.Numerics /// the string to parse. /// /// - /// An that supplies culture-specific formatting information. + /// An that supplies culture-specific + /// formatting information. /// public static Complex Parse(string value, IFormatProvider formatProvider) { @@ -1083,8 +1085,6 @@ namespace MathNet.Numerics throw new FormatException(); } - value = value.Replace(" ", string.Empty); - // strip out parens if (value.StartsWith("(", StringComparison.Ordinal)) { @@ -1093,77 +1093,137 @@ namespace MathNet.Numerics throw new FormatException(); } - value = value.Substring(1, value.Length - 2); + value = value.Substring(1, value.Length - 2).Trim(); } - // check if one character strings are valid - if (value.Length == 1) - { - if (String.Compare(value, "i", StringComparison.OrdinalIgnoreCase) == 0) + // keywords + var numberFormatInfo = formatProvider.GetNumberFormatInfo(); + var textInfo = formatProvider.GetTextInfo(); + var keywords = + new[] { - return new Complex(0, 1); - } - - return new Complex(Double.Parse(value, formatProvider), 0.0); + textInfo.ListSeparator, numberFormatInfo.NaNSymbol, + numberFormatInfo.NegativeInfinitySymbol, numberFormatInfo.PositiveInfinitySymbol, + "+", "-", "i", "j" + }; + + // lexing + var tokens = new LinkedList(); + GlobalizationHelper.Tokenize(tokens.AddFirst(value), keywords, 0); + var token = tokens.First; + + // parse the left part + bool isLeftPartImaginary; + double leftPart = ParsePart(ref token, out isLeftPartImaginary, formatProvider); + if (token == null) + { + return isLeftPartImaginary ? new Complex(0, leftPart) : new Complex(leftPart, 0); } - if (value.Equals("-i")) + // parse the right part + if (token.Value == textInfo.ListSeparator) { - return new Complex(0, -1); - } + // format: real,imag + token = token.Next; - var real = 0.0; - var imag = 0.0; + if (isLeftPartImaginary) + { + // left must not contain 'i', right doesn't matter. + throw new FormatException(); + } - var index = value.IndexOf(','); + bool isRightPartImaginary; + double rightPart = ParsePart(ref token, out isRightPartImaginary, formatProvider); - if (index > -1) + return new Complex(leftPart, rightPart); + } + else { - real = double.Parse(value.Substring(0, index), formatProvider); - var imagStr = value.Substring(index + 1, value.Length - index - 1); - if (imagStr.EndsWith("i")) + // format: real + imag + bool isRightPartImaginary; + double rightPart = ParsePart(ref token, out isRightPartImaginary, formatProvider); + + if (!(isLeftPartImaginary ^ isRightPartImaginary)) { - imagStr = imagStr.Substring(0, imagStr.Length - 1); + // either left or right part must contain 'i', but not both. + throw new FormatException(); } - imag = double.Parse(imagStr, formatProvider); + return isLeftPartImaginary ? new Complex(rightPart, leftPart) : new Complex(leftPart, rightPart); } - else + } + + /// + /// Parse a part (real or complex) from a complex number. + /// + /// Start Token. + /// Is set to true if the part identified itself as being imaginary. + /// + /// An that supplies culture-specific + /// formatting information. + /// + /// Resulting part as double. + /// + private static double ParsePart(ref LinkedListNode token, out bool imaginary, IFormatProvider format) + { + imaginary = false; + if (token == null) + { + throw new FormatException(); + } + + // handle prefix modifiers + if (token.Value == "+") { - var matchResult = _parseExpression.Match(value); - if (matchResult.Success) + token = token.Next; + + if (token == null) { - var realStr = matchResult.Groups["r"].Value; - if (!string.IsNullOrEmpty(realStr)) - { - if (realStr.StartsWith("+")) - { - realStr = realStr.Substring(1); - } + throw new FormatException(); + } + } - real = double.Parse(realStr, formatProvider); - } + bool negative = false; + if (token.Value == "-") + { + negative = true; + token = token.Next; - var imagStr = matchResult.Groups["i"].Value; + if (token == null) + { + throw new FormatException(); + } + } - if (!string.IsNullOrEmpty(imagStr)) - { - if (imagStr.StartsWith("+")) - { - imagStr = imagStr.Substring(1); - } + // handle prefix imaginary symbol + if (String.Compare(token.Value, "i", StringComparison.OrdinalIgnoreCase) == 0 + || String.Compare(token.Value, "j", StringComparison.OrdinalIgnoreCase) == 0) + { + imaginary = true; + token = token.Next; - imagStr = imagStr.Substring(0, imagStr.Length - 1); - imag = double.Parse(imagStr, formatProvider); - } + if (token == null) + { + return negative ? -1 : 1; } - else + } + + double value = GlobalizationHelper.ParseDouble(ref token, format.GetCultureInfo()); + + // handle suffix imaginary symbol + if (token != null && String.Compare(token.Value, "i", StringComparison.OrdinalIgnoreCase) == 0) + { + if (imaginary) { + // only one time allowed: either prefix or suffix, or neither. throw new FormatException(); } + + imaginary = true; + token = token.Next; } - return new Complex(real, imag); + return negative ? -value : value; } /// @@ -1226,4 +1286,4 @@ namespace MathNet.Numerics #endregion } -} +} \ No newline at end of file diff --git a/src/UnitTests/ComplexTests/ComplexTest.cs b/src/UnitTests/ComplexTests/ComplexTest.cs index d6c1cd6e..89ace103 100644 --- a/src/UnitTests/ComplexTests/ComplexTest.cs +++ b/src/UnitTests/ComplexTests/ComplexTest.cs @@ -1,8 +1,34 @@ -namespace MathNet.Numerics.UnitTests +// +// Math.NET Numerics, part of the Math.NET Project +// http://mathnet.opensourcedotnet.info +// +// Copyright (c) 2009 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.UnitTests.ComplexTests { using System; - using System.Globalization; - using MbUnit.Framework; [TestFixture] @@ -129,8 +155,7 @@ AssertHelpers.AlmostEqual(new Complex(double.PositiveInfinity, double.PositiveInfinity), a.Power(b), 15); a = new Complex(0.0, 0.0); b = new Complex(0.0, 1.0); - AssertEx.That(()=>a.Power(b).IsNaN); - + AssertEx.That(() => a.Power(b).IsNaN); } [Test] @@ -200,18 +225,6 @@ AssertHelpers.AlmostEqual(Complex.Zero, complex.SquareRoot(), 15); } - [Test] - [Row(1, -2, "1 -2i")] - [Row(1, 2, "1 + 2i")] - [Row(1, 0, "1")] - [Row(0, -2, "-2i")] - [Row(0, 2, "2i")] - public void CanConvertComplexToString(double real, double imag, string expected) - { - var a = new Complex(real, imag); - Assert.AreEqual(expected, a.ToString()); - } - [Test] [MultipleAsserts] public void CanConvertDoubleToComplex() @@ -221,68 +234,6 @@ Assert.AreEqual(1.1, new Complex(1.1, 0)); } - [Test] - [Row("-1", -1, 0)] - [Row("-i", 0, -1)] - [Row("i", 0, 1)] - [Row("2i", 0, 2)] - [Row("1 + 2i", 1, 2)] - [Row("1+2i", 1, 2)] - [Row("1 - 2i", 1, -2)] - [Row("1-2i", 1, -2)] - [Row("1,2", 1, 2)] - [Row("1 , 2", 1, 2)] - [Row("1,2i", 1, 2)] - [Row("-1, -2i", -1, -2)] - [Row("(+1,2i)", 1, 2)] - [Row("(-1 , -2)", -1, -2)] - [Row("(-1 , -2i)", -1, -2)] - [Row("(+1e1 , -2e-2i)", 10, -0.02)] - [Row("(-1E1 -2e2i)", -10, -200)] - [Row("(-1e+1 -2e2i)", -10, -200)] - [Row("(-1e1 -2e+2i)", -10, -200)] - [Row("(-1e-1 -2E2i)", -0.1, -200)] - [Row("(-1e1 -2e-2i)", -10, -0.02)] - [Row("(-1E+1 -2e+2i)", -10, -200)] - [Row("(-1e-1,-2e-2i)", -0.1, -0.02)] - [Row("(+1 +2i)", 1, 2)] - public void CanConvertStringToComplexUsingTryParse(string str, double expectedReal, double expectedImag) - { - Complex z; - var ret = Complex.TryParse(str, out z); - Assert.IsTrue(ret); - Assert.AreEqual(expectedReal, z.Real); - Assert.AreEqual(expectedImag, z.Imaginary); - - ret = Complex.TryParse("(-1E+1 -2e+2i)", out z); - Assert.IsTrue(ret); - Assert.AreEqual(-10, z.Real); - Assert.AreEqual(-200, z.Imaginary); - - ret = Complex.TryParse("(-1e-1,-2e-2i)", out z); - Assert.IsTrue(ret); - Assert.AreEqual(-.1, z.Real); - Assert.AreEqual(-.02, z.Imaginary); - - ret = Complex.TryParse("(+1 +2i)", out z); - Assert.IsTrue(ret); - Assert.AreEqual(1, z.Real); - Assert.AreEqual(2, z.Imaginary); - } - - [Test] - public void CanParseStringToComplex() - { - var actual = Complex.Parse("-1 -2i"); - Assert.AreEqual(new Complex(-1,-2), actual); - } - - [Test] - public void ParseThrowsFormatExceptionIfMissingClosingParen() - { - Assert.Throws(() => Complex.Parse("(1,2")); - } - [Test] [MultipleAsserts] public void CanCreateComplexNumberUsingTheConstructor() @@ -310,43 +261,6 @@ Assert.AreEqual(-2.2, complex.Imaginary, "Imaginary part is -2.2."); } - [Test] - [MultipleAsserts] - public void CanCreateStringFromComplexNumber() - { - Assert.AreEqual("NaN", Complex.NaN.ToString()); - Assert.AreEqual("Infinity", Complex.Infinity.ToString()); - Assert.AreEqual("1.1", new Complex(1.1, 0).ToString()); - Assert.AreEqual("-1.1i", new Complex(0, -1.1).ToString()); - Assert.AreEqual("1.1i", new Complex(0, 1.1).ToString()); - Assert.AreEqual("1.1 + 1.1i", new Complex(1.1, 1.1).ToString()); - } - - [Test] - [MultipleAsserts] - public void CanCreateStringUsingFormatProvider() - { - var provider = CultureInfo.GetCultureInfo("tr-TR"); - Assert.AreEqual("NaN", Complex.NaN.ToString(provider)); - Assert.AreEqual("Infinity", Complex.Infinity.ToString(provider)); - Assert.AreEqual("1,1", new Complex(1.1, 0).ToString(provider)); - Assert.AreEqual("-1,1i", new Complex(0, -1.1).ToString(provider)); - Assert.AreEqual("1,1i", new Complex(0, 1.1).ToString(provider)); - Assert.AreEqual("1,1 + 1,1i", new Complex(1.1, 1.1).ToString(provider)); - } - - [Test] - [MultipleAsserts] - public void CanCreateStringUsingNumberFormat() - { - Assert.AreEqual("NaN", Complex.NaN.ToString("#.000")); - Assert.AreEqual("Infinity", Complex.Infinity.ToString("#.000")); - Assert.AreEqual("1.100", new Complex(1.1, 0).ToString("#.000")); - Assert.AreEqual("-1.100i", new Complex(0, -1.1).ToString("#.000")); - Assert.AreEqual("1.100i", new Complex(0, 1.1).ToString("#.000")); - Assert.AreEqual("1.100 + 1.100i", new Complex(1.1, 1.1).ToString("#.000")); - } - [Test] public void CanDetermineIfImaginaryUnit() { @@ -532,51 +446,6 @@ Assert.AreEqual(complex, +complex); } - [Test] - public void TryParseCanHandleSymbols() - { - Complex z; - var ni = new NumberFormatInfo(); - var ret = Complex.TryParse(ni.NegativeInfinitySymbol + "," + ni.PositiveInfinitySymbol, out z); - Assert.IsTrue(ret); - Assert.AreEqual(double.NegativeInfinity, z.Real); - Assert.AreEqual(double.PositiveInfinity, z.Imaginary); - - ret = Complex.TryParse(ni.NaNSymbol + "," + ni.NaNSymbol, out z); - Assert.IsTrue(ret); - Assert.AreEqual(double.NaN, z.Real); - Assert.AreEqual(double.NaN, z.Imaginary); - - ret = Complex.TryParse(ni.NegativeInfinitySymbol + "+" + ni.PositiveInfinitySymbol + "i", out z); - Assert.IsTrue(ret); - Assert.AreEqual(double.NegativeInfinity, z.Real); - Assert.AreEqual(double.PositiveInfinity, z.Imaginary); - - ret = Complex.TryParse(ni.NaNSymbol + "+" + ni.NaNSymbol + "i", out z); - Assert.IsTrue(ret); - Assert.AreEqual(double.NaN, z.Real); - Assert.AreEqual(double.NaN, z.Imaginary); - - ret = Complex.TryParse(double.MaxValue.ToString("R") + " " + double.MinValue.ToString("R") + "i", out z); - Assert.IsTrue(ret); - Assert.AreEqual(double.MaxValue, z.Real); - Assert.AreEqual(double.MinValue, z.Imaginary); - } - - [Test] - [Row("")] - [Row("+")] - [Row("1i+2")] - [Row(null)] - public void TryParseReturnsFalseWhenGiveBadValue(string str) - { - Complex z; - var ret = Complex.TryParse(str, out z); - Assert.IsFalse(ret); - Assert.AreEqual(0, z.Real); - Assert.AreEqual(0, z.Imaginary); - } - [Test] public void WithModulusArgumentThrowsArgumentOutOfRangeException() { diff --git a/src/UnitTests/ComplexTests/ComplexTextHandlingTest.cs b/src/UnitTests/ComplexTests/ComplexTextHandlingTest.cs new file mode 100644 index 00000000..4c5050ef --- /dev/null +++ b/src/UnitTests/ComplexTests/ComplexTextHandlingTest.cs @@ -0,0 +1,282 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://mathnet.opensourcedotnet.info +// +// Copyright (c) 2009 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.UnitTests.ComplexTests +{ + using System; + using System.Globalization; + using MbUnit.Framework; + + [TestFixture] + public class ComplexTextHandlingTest + { + [Test] + [Row(1, -2, "1 -2i")] + [Row(1, 2, "1 + 2i")] + [Row(1, 0, "1")] + [Row(0, -2, "-2i")] + [Row(0, 2, "2i")] + [Row(0, 2, "2i")] + [Row(0, 0, "0")] + [Row(Double.NaN, Double.NaN, "{1}")] + [Row(Double.NaN, 0, "{1}")] + [Row(0, Double.NaN, "{1}")] + [Row(Double.PositiveInfinity, Double.PositiveInfinity, "{2}")] + [Row(1.1, 0, "1{0}1")] + [Row(-1.1, 0, "-1{0}1")] + [Row(0, 1.1, "1{0}1i")] + [Row(0, -1.1, "-1{0}1i")] + [Row(1.1, 1.1, "1{0}1 + 1{0}1i")] + public void CanFormatComplexToString(double real, double imag, string expected) + { + var numberFormat = NumberFormatInfo.CurrentInfo; + var a = new Complex(real, imag); + Assert.AreEqual( + String.Format( + expected, + numberFormat.NumberDecimalSeparator, + numberFormat.NaNSymbol, + numberFormat.PositiveInfinitySymbol), + a.ToString()); + } + + [Test] + [MultipleAsserts] + [Row("en-US", "NaN", "Infinity", "1.1")] + [Row("tr-TR", "NaN", "Infinity", "1,1")] + [Row("de-DE", "n. def.", "+unendlich", "1,1")] + [Row("de-CH", "n. def.", "+unendlich", "1.1")] + [Row("he-IL", "לא מספר", "אינסוף חיובי", "1.1")] + public void CanFormatComplexToStringWithCulture( + string cultureName, string nan, string infinity, string number) + { + var provider = CultureInfo.GetCultureInfo(cultureName); + Assert.AreEqual(nan, Complex.NaN.ToString(provider)); + Assert.AreEqual(infinity, Complex.Infinity.ToString(provider)); + Assert.AreEqual("0", Complex.Zero.ToString(provider)); + Assert.AreEqual(String.Format("{0}", number), new Complex(1.1, 0).ToString(provider)); + Assert.AreEqual(String.Format("-{0}", number), new Complex(-1.1, 0).ToString(provider)); + Assert.AreEqual(String.Format("-{0}i", number), new Complex(0, -1.1).ToString(provider)); + Assert.AreEqual(String.Format("{0}i", number), new Complex(0, 1.1).ToString(provider)); + Assert.AreEqual(String.Format("{0} + {0}i", number), new Complex(1.1, 1.1).ToString(provider)); + } + + [Test] + [MultipleAsserts] + public void CanFormatComplexToStringWithFormat() + { + Assert.AreEqual("0", String.Format("{0:G}", Complex.Zero)); + Assert.AreEqual("1 + 2i", String.Format("{0:G}", new Complex(1, 2))); + Assert.AreEqual("001 + 002i", String.Format("{0:000;minus 000;zero}", new Complex(1, 2))); + Assert.AreEqual("minus 002i", String.Format("{0:000;minus 000;zero}", new Complex(0, -2))); + Assert.AreEqual("zero", String.Format("{0:000;minus 000;zero}", Complex.Zero)); + + Assert.AreEqual("0", Complex.Zero.ToString("G")); + Assert.AreEqual("1 + 2i", new Complex(1, 2).ToString("G")); + Assert.AreEqual("001 + 002i", new Complex(1, 2).ToString("#000;minus 000;zero")); + Assert.AreEqual("minus 002i", new Complex(0, -2).ToString("#000;minus 000;zero")); + Assert.AreEqual("zero", Complex.Zero.ToString("#000;minus 000;zero")); + } + + [Test] + [MultipleAsserts] + public void CanFormatComplexToStringWithFormatInvariant() + { + var culture = CultureInfo.InvariantCulture; + + Assert.AreEqual("NaN", String.Format(culture, "{0:.000}", Complex.NaN)); + Assert.AreEqual(".000", String.Format(culture, "{0:.000}", Complex.Zero)); + Assert.AreEqual("1.100", String.Format(culture, "{0:.000}", new Complex(1.1, 0))); + Assert.AreEqual("1.100 + 1.100i", String.Format(culture, "{0:.000}", new Complex(1.1, 1.1))); + + Assert.AreEqual("NaN", Complex.NaN.ToString("#.000", culture)); + Assert.AreEqual("Infinity", Complex.Infinity.ToString("#.000", culture)); + Assert.AreEqual(".000", Complex.Zero.ToString("#.000", culture)); + Assert.AreEqual("1.100", new Complex(1.1, 0).ToString("#.000", culture)); + Assert.AreEqual("-1.100i", new Complex(0, -1.1).ToString("#.000", culture)); + Assert.AreEqual("1.100i", new Complex(0, 1.1).ToString("#.000", culture)); + Assert.AreEqual("1.100 + 1.100i", new Complex(1.1, 1.1).ToString("#.000", culture)); + } + + [Test] + [Row("-1 -2i", -1, -2, "en-US")] + [Row("-1 - 2i ", -1, -2, "de-CH")] + public void CanParseStringToComplexWithCulture( + string text, double expectedReal, double expectedImaginary, string cultureName) + { + Complex parsed = Complex.Parse(text, CultureInfo.GetCultureInfo(cultureName)); + Assert.AreEqual(expectedReal, parsed.Real); + Assert.AreEqual(expectedImaginary, parsed.Imaginary); + } + + [Test] + [Row("1", 1, 0)] + [Row("-1", -1, 0)] + [Row("-i", 0, -1)] + [Row("i", 0, 1)] + [Row("2i", 0, 2)] + [Row("1 + 2i", 1, 2)] + [Row("1+2i", 1, 2)] + [Row("1 - 2i", 1, -2)] + [Row("1-2i", 1, -2)] + [Row("1,2 ", 1, 2)] + [Row("1 , 2", 1, 2)] + [Row("1,2i", 1, 2)] + [Row("-1, -2i", -1, -2)] + [Row(" - 1 , - 2 i ", -1, -2)] + [Row("(+1,2i)", 1, 2)] + [Row("(-1 , -2)", -1, -2)] + [Row("(-1 , -2i)", -1, -2)] + [Row("(+1e1 , -2e-2i)", 10, -0.02)] + [Row("(-1E1 -2e2i)", -10, -200)] + [Row("(-1e+1 -2e2i)", -10, -200)] + [Row("(-1e1 -2e+2i)", -10, -200)] + [Row("(-1e-1 -2E2i)", -0.1, -200)] + [Row("(-1e1 -2e-2i)", -10, -0.02)] + [Row("(-1E+1 -2e+2i)", -10, -200)] + [Row("(-1e-1,-2e-2i)", -0.1, -0.02)] + [Row("(+1 +2i)", 1, 2)] + [Row("(-1E+1 -2e+2i)", -10, -200)] + [Row("(-1e-1,-2e-2i)", -0.1, -0.02)] + public void CanTryParseStringToComplexWithInvariant(string str, double expectedReal, double expectedImaginary) + { + var invariantCulture = CultureInfo.InvariantCulture; + Complex z; + var ret = Complex.TryParse(str, invariantCulture, out z); + Assert.IsTrue(ret); + Assert.AreEqual(expectedReal, z.Real); + Assert.AreEqual(expectedImaginary, z.Imaginary); + } + + [Test] + public void ParseThrowsFormatExceptionIfMissingClosingParen() + { + Assert.Throws(() => Complex.Parse("(1,2")); + } + + [Test] + public void TryParseCanHandleSymbols() + { + Complex z; + var ni = NumberFormatInfo.CurrentInfo; + var separator = CultureInfo.CurrentCulture.TextInfo.ListSeparator; + var ret = Complex.TryParse( + ni.NegativeInfinitySymbol + separator + ni.PositiveInfinitySymbol, out z); + Assert.IsTrue(ret, "A1"); + Assert.AreEqual(double.NegativeInfinity, z.Real, "A2"); + Assert.AreEqual(double.PositiveInfinity, z.Imaginary, "A3"); + + ret = Complex.TryParse(ni.NaNSymbol + separator + ni.NaNSymbol, out z); + Assert.IsTrue(ret, "B1"); + Assert.AreEqual(double.NaN, z.Real, "B2"); + Assert.AreEqual(double.NaN, z.Imaginary, "B3"); + + ret = Complex.TryParse(ni.NegativeInfinitySymbol + "+" + ni.PositiveInfinitySymbol + "i", out z); + Assert.IsTrue(ret, "C1"); + Assert.AreEqual(double.NegativeInfinity, z.Real, "C2"); + Assert.AreEqual(double.PositiveInfinity, z.Imaginary, "C3"); + + ret = Complex.TryParse(ni.NaNSymbol + "+" + ni.NaNSymbol + "i", out z); + Assert.IsTrue(ret, "D1"); + Assert.AreEqual(double.NaN, z.Real, "D2"); + Assert.AreEqual(double.NaN, z.Imaginary, "D3"); + + ret = Complex.TryParse( + double.MaxValue.ToString("R") + " " + double.MinValue.ToString("R") + "i", + out z); + Assert.IsTrue(ret, "E1"); + Assert.AreEqual(double.MaxValue, z.Real, "E2"); + Assert.AreEqual(double.MinValue, z.Imaginary, "E3"); + } + + [Test] + [Row("en-US")] + [Row("tr-TR")] + [Row("de-DE")] + [Row("de-CH")] + [Row("he-IL")] + public void TryParseCanHandleSymbolsWithCulture(string cultureName) + { + Complex z; + var culture = CultureInfo.GetCultureInfo(cultureName); + var ni = culture.NumberFormat; + var separator = culture.TextInfo.ListSeparator; + var ret = Complex.TryParse( + ni.NegativeInfinitySymbol + separator + ni.PositiveInfinitySymbol, culture, out z); + Assert.IsTrue(ret, "A1"); + Assert.AreEqual(double.NegativeInfinity, z.Real, "A2"); + Assert.AreEqual(double.PositiveInfinity, z.Imaginary, "A3"); + + ret = Complex.TryParse(ni.NaNSymbol + separator + ni.NaNSymbol, culture, out z); + Assert.IsTrue(ret, "B1"); + Assert.AreEqual(double.NaN, z.Real, "B2"); + Assert.AreEqual(double.NaN, z.Imaginary, "B3"); + + ret = Complex.TryParse(ni.NegativeInfinitySymbol + "+" + ni.PositiveInfinitySymbol + "i", culture, out z); + Assert.IsTrue(ret, "C1"); + Assert.AreEqual(double.NegativeInfinity, z.Real, "C2"); + Assert.AreEqual(double.PositiveInfinity, z.Imaginary, "C3"); + + ret = Complex.TryParse(ni.NaNSymbol + "+" + ni.NaNSymbol + "i", culture, out z); + Assert.IsTrue(ret, "D1"); + Assert.AreEqual(double.NaN, z.Real, "D2"); + Assert.AreEqual(double.NaN, z.Imaginary, "D3"); + + ret = Complex.TryParse( + double.MaxValue.ToString("R", culture) + " " + double.MinValue.ToString("R", culture) + "i", + culture, + out z); + Assert.IsTrue(ret, "E1"); + Assert.AreEqual(double.MaxValue, z.Real, "E2"); + Assert.AreEqual(double.MinValue, z.Imaginary, "E3"); + } + + [Test] + [Row("")] + [Row("+")] + [Row("1-")] + [Row("i+")] + [Row("1/2i")] + [Row("1i+2i")] + [Row("i1i")] + [Row("(1i,2)")] + [Row("1e+")] + [Row("1e")] + [Row("1,")] + [Row(",1")] + [Row(null)] + public void TryParseReturnsFalseWhenGiveBadValueWithInvariant(string str) + { + Complex z; + var ret = Complex.TryParse(str, CultureInfo.InvariantCulture, out z); + Assert.IsFalse(ret); + Assert.AreEqual(0, z.Real); + Assert.AreEqual(0, z.Imaginary); + } + } +} \ No newline at end of file diff --git a/src/UnitTests/UnitTests.csproj b/src/UnitTests/UnitTests.csproj index 11092c1f..3e24b05d 100644 --- a/src/UnitTests/UnitTests.csproj +++ b/src/UnitTests/UnitTests.csproj @@ -62,6 +62,7 @@ +