From: Martin Midtgaard Date: Fri, 7 Feb 2014 10:21:49 +0000 (+0100) Subject: Implemented overloaded versions of Parse and TryParse functions for BigInteger. X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=commitdiff_plain;h=61a2e43f49beceeaef895955016285478fe0f75a;p=mono.git Implemented overloaded versions of Parse and TryParse functions for BigInteger. Implementation is heavily inspired by Int32. Added unit tests for the new functions. --- diff --git a/mcs/class/System.Numerics/System.Numerics.dll.sources b/mcs/class/System.Numerics/System.Numerics.dll.sources index c985f7faa80..ff70864f818 100644 --- a/mcs/class/System.Numerics/System.Numerics.dll.sources +++ b/mcs/class/System.Numerics/System.Numerics.dll.sources @@ -1,5 +1,4 @@ ../../build/common/Consts.cs -../../build/common/MonoTODOAttribute.cs Assembly/AssemblyInfo.cs System.Numerics/Complex.cs System.Numerics/BigInteger.cs \ No newline at end of file diff --git a/mcs/class/System.Numerics/System.Numerics/BigInteger.cs b/mcs/class/System.Numerics/System.Numerics/BigInteger.cs index 816ca2ffcee..da634fdc5f4 100644 --- a/mcs/class/System.Numerics/System.Numerics/BigInteger.cs +++ b/mcs/class/System.Numerics/System.Numerics/BigInteger.cs @@ -1467,36 +1467,426 @@ namespace System.Numerics { Exception ex; return Parse (value, true, out result, out ex); } - -#if NET_4_0 - [MonoTODO] + public static BigInteger Parse (string value, NumberStyles style) { - throw new NotImplementedException (); + return Parse (value, style, null); } - [MonoTODO] public static BigInteger Parse (string value, IFormatProvider provider) { - throw new NotImplementedException (); + return Parse (value, NumberStyles.Integer, provider); } - [MonoTODO] public static BigInteger Parse ( string value, NumberStyles style, IFormatProvider provider) { - throw new InvalidOperationException (); + Exception exc; + BigInteger res; + + if (!Parse (value, style, provider, false, out res, out exc)) + throw exc; + + return res; } - - [MonoTODO] + public static bool TryParse ( string value, NumberStyles style, IFormatProvider provider, out BigInteger result) { - throw new NotImplementedException (); + Exception exc; + if (!Parse (value, style, provider, true, out result, out exc)) { + result = Zero; + return false; + } + + return true; + } + + internal static bool Parse (string s, NumberStyles style, IFormatProvider fp, bool tryParse, out BigInteger result, out Exception exc) + { + result = Zero; + exc = null; + + if (s == null) { + if (!tryParse) + exc = new ArgumentNullException ("s"); + return false; + } + + if (s.Length == 0) { + if (!tryParse) + exc = GetFormatException (); + return false; + } + + NumberFormatInfo nfi = null; + if (fp != null) { + Type typeNFI = typeof(System.Globalization.NumberFormatInfo); + nfi = (NumberFormatInfo)fp.GetFormat (typeNFI); + } + if (nfi == null) + nfi = Thread.CurrentThread.CurrentCulture.NumberFormat; + + if (!CheckStyle (style, tryParse, ref exc)) + return false; + + bool AllowCurrencySymbol = (style & NumberStyles.AllowCurrencySymbol) != 0; + bool AllowHexSpecifier = (style & NumberStyles.AllowHexSpecifier) != 0; + bool AllowThousands = (style & NumberStyles.AllowThousands) != 0; + bool AllowDecimalPoint = (style & NumberStyles.AllowDecimalPoint) != 0; + bool AllowParentheses = (style & NumberStyles.AllowParentheses) != 0; + bool AllowTrailingSign = (style & NumberStyles.AllowTrailingSign) != 0; + bool AllowLeadingSign = (style & NumberStyles.AllowLeadingSign) != 0; + bool AllowTrailingWhite = (style & NumberStyles.AllowTrailingWhite) != 0; + bool AllowLeadingWhite = (style & NumberStyles.AllowLeadingWhite) != 0; + bool AllowExponent = (style & NumberStyles.AllowExponent) != 0; + + int pos = 0; + + if (AllowLeadingWhite && !JumpOverWhite (ref pos, s, true, tryParse, ref exc)) + return false; + + bool foundOpenParentheses = false; + bool negative = false; + bool foundSign = false; + bool foundCurrency = false; + + // Pre-number stuff + if (AllowParentheses && s [pos] == '(') { + foundOpenParentheses = true; + foundSign = true; + negative = true; // MS always make the number negative when there parentheses + // even when NumberFormatInfo.NumberNegativePattern != 0!!! + pos++; + if (AllowLeadingWhite && !JumpOverWhite (ref pos, s, true, tryParse, ref exc)) + return false; + + if (s.Substring (pos, nfi.NegativeSign.Length) == nfi.NegativeSign) { + if (!tryParse) + exc = GetFormatException (); + return false; + } + + if (s.Substring (pos, nfi.PositiveSign.Length) == nfi.PositiveSign) { + if (!tryParse) + exc = GetFormatException (); + return false; + } + } + + if (AllowLeadingSign && !foundSign) { + // Sign + Currency + FindSign (ref pos, s, nfi, ref foundSign, ref negative); + if (foundSign) { + if (AllowLeadingWhite && !JumpOverWhite (ref pos, s, true, tryParse, ref exc)) + return false; + if (AllowCurrencySymbol) { + FindCurrency (ref pos, s, nfi, + ref foundCurrency); + if (foundCurrency && AllowLeadingWhite && + !JumpOverWhite (ref pos, s, true, tryParse, ref exc)) + return false; + } + } + } + + if (AllowCurrencySymbol && !foundCurrency) { + // Currency + sign + FindCurrency (ref pos, s, nfi, ref foundCurrency); + if (foundCurrency) { + if (AllowLeadingWhite && !JumpOverWhite (ref pos, s, true, tryParse, ref exc)) + return false; + if (foundCurrency) { + if (!foundSign && AllowLeadingSign) { + FindSign (ref pos, s, nfi, ref foundSign, + ref negative); + if (foundSign && AllowLeadingWhite && + !JumpOverWhite (ref pos, s, true, tryParse, ref exc)) + return false; + } + } + } + } + + BigInteger number = Zero; + int nDigits = 0; + int decimalPointPos = -1; + byte digitValue; + char hexDigit; + bool firstHexDigit = true; + + // Number stuff + while (pos < s.Length) { + + if (!ValidDigit (s [pos], AllowHexSpecifier)) { + if (AllowThousands && + (FindOther (ref pos, s, nfi.NumberGroupSeparator) + || FindOther (ref pos, s, nfi.CurrencyGroupSeparator))) + continue; + + if (AllowDecimalPoint && decimalPointPos < 0 && + (FindOther (ref pos, s, nfi.NumberDecimalSeparator) + || FindOther (ref pos, s, nfi.CurrencyDecimalSeparator))) { + decimalPointPos = nDigits; + continue; + } + + break; + } + + nDigits++; + + if (AllowHexSpecifier) { + hexDigit = s [pos++]; + if (Char.IsDigit (hexDigit)) + digitValue = (byte)(hexDigit - '0'); + else if (Char.IsLower (hexDigit)) + digitValue = (byte)(hexDigit - 'a' + 10); + else + digitValue = (byte)(hexDigit - 'A' + 10); + + if (firstHexDigit && (byte)digitValue >= 8) + negative = true; + + number = number * 16 + digitValue; + firstHexDigit = false; + continue; + } + + number = number * 10 + (byte)(s [pos++] - '0'); + } + + // Post number stuff + if (nDigits == 0) { + if (!tryParse) + exc = GetFormatException (); + return false; + } + + //signed hex value + if (AllowHexSpecifier && negative) { + //number = 123456; + BigInteger mask = BigInteger.Pow(16, nDigits) - 1; + number = (number ^ mask) + 1; + } + + int exponent = 0; + if (AllowExponent) + if (FindExponent (ref pos, s, ref exponent, tryParse, ref exc) && exc != null) + return false; + + if (AllowTrailingSign && !foundSign) { + // Sign + Currency + FindSign (ref pos, s, nfi, ref foundSign, ref negative); + if (foundSign && pos < s.Length) { + if (AllowTrailingWhite && !JumpOverWhite (ref pos, s, true, tryParse, ref exc)) + return false; + } + } + + if (AllowCurrencySymbol && !foundCurrency) { + if (AllowTrailingWhite && pos < s.Length && !JumpOverWhite (ref pos, s, false, tryParse, ref exc)) + return false; + + // Currency + sign + FindCurrency (ref pos, s, nfi, ref foundCurrency); + if (foundCurrency && pos < s.Length) { + if (AllowTrailingWhite && !JumpOverWhite (ref pos, s, true, tryParse, ref exc)) + return false; + if (!foundSign && AllowTrailingSign) + FindSign (ref pos, s, nfi, ref foundSign, + ref negative); + } + } + + if (AllowTrailingWhite && pos < s.Length && !JumpOverWhite (ref pos, s, false, tryParse, ref exc)) + return false; + + if (foundOpenParentheses) { + if (pos >= s.Length || s [pos++] != ')') { + if (!tryParse) + exc = GetFormatException (); + return false; + } + if (AllowTrailingWhite && pos < s.Length && !JumpOverWhite (ref pos, s, false, tryParse, ref exc)) + return false; + } + + if (pos < s.Length && s [pos] != '\u0000') { + if (!tryParse) + exc = GetFormatException (); + return false; + } + + if (decimalPointPos >= 0) + exponent = exponent - nDigits + decimalPointPos; + + if (exponent < 0) { + // + // Any non-zero values after decimal point are not allowed + // + BigInteger remainder; + number = BigInteger.DivRem(number, BigInteger.Pow(10, -exponent), out remainder); + + if (!remainder.IsZero) { + if (!tryParse) + exc = new OverflowException ("Value too large or too small. exp="+exponent+" rem = " + remainder + " pow = " + BigInteger.Pow(10, -exponent)); + return false; + } + } else if (exponent > 0) { + number = BigInteger.Pow(10, exponent) * number; + } + + if (number.sign == 0) + result = number; + else if (negative) + result = new BigInteger (-1, number.data); + else + result = new BigInteger (1, number.data); + + return true; + } + + internal static bool CheckStyle (NumberStyles style, bool tryParse, ref Exception exc) + { + if ((style & NumberStyles.AllowHexSpecifier) != 0) { + NumberStyles ne = style ^ NumberStyles.AllowHexSpecifier; + if ((ne & NumberStyles.AllowLeadingWhite) != 0) + ne ^= NumberStyles.AllowLeadingWhite; + if ((ne & NumberStyles.AllowTrailingWhite) != 0) + ne ^= NumberStyles.AllowTrailingWhite; + if (ne != 0) { + if (!tryParse) + exc = new ArgumentException ( + "With AllowHexSpecifier only " + + "AllowLeadingWhite and AllowTrailingWhite " + + "are permitted."); + return false; + } + } else if ((uint) style > (uint) NumberStyles.Any){ + if (!tryParse) + exc = new ArgumentException ("Not a valid number style"); + return false; + } + + return true; } -#endif + internal static bool JumpOverWhite (ref int pos, string s, bool reportError, bool tryParse, ref Exception exc) + { + while (pos < s.Length && Char.IsWhiteSpace (s [pos])) + pos++; + + if (reportError && pos >= s.Length) { + if (!tryParse) + exc = GetFormatException (); + return false; + } + + return true; + } + + internal static void FindSign (ref int pos, string s, NumberFormatInfo nfi, + ref bool foundSign, ref bool negative) + { + if ((pos + nfi.NegativeSign.Length) <= s.Length && + string.CompareOrdinal(s, pos, nfi.NegativeSign, 0, nfi.NegativeSign.Length) == 0) { + negative = true; + foundSign = true; + pos += nfi.NegativeSign.Length; + } else if ((pos + nfi.PositiveSign.Length) <= s.Length && + string.CompareOrdinal(s, pos, nfi.PositiveSign, 0, nfi.PositiveSign.Length) == 0) { + negative = false; + pos += nfi.PositiveSign.Length; + foundSign = true; + } + } + + internal static void FindCurrency (ref int pos, + string s, + NumberFormatInfo nfi, + ref bool foundCurrency) + { + if ((pos + nfi.CurrencySymbol.Length) <= s.Length && + s.Substring (pos, nfi.CurrencySymbol.Length) == nfi.CurrencySymbol) { + foundCurrency = true; + pos += nfi.CurrencySymbol.Length; + } + } + + internal static bool FindExponent (ref int pos, string s, ref int exponent, bool tryParse, ref Exception exc) + { + exponent = 0; + + if (pos >= s.Length || (s [pos] != 'e' && s[pos] != 'E')) { + exc = null; + return false; + } + + var i = pos + 1; + if (i == s.Length) { + exc = tryParse ? null : GetFormatException (); + return true; + } + + bool negative = false; + if (s [i] == '-') { + negative = true; + if(++i == s.Length){ + exc = tryParse ? null : GetFormatException (); + return true; + } + } + + if (s [i] == '+' && ++i == s.Length) { + exc = tryParse ? null : GetFormatException (); + return true; + } + + long exp = 0; // temp long value + for (; i < s.Length; i++) { + if (!Char.IsDigit (s [i])) { + exc = tryParse ? null : GetFormatException (); + return true; + } + + // Reduce the risk of throwing an overflow exc + exp = checked (exp * 10 - (int) (s [i] - '0')); + if (exp < Int32.MinValue || exp > Int32.MaxValue) { + exc = tryParse ? null : new OverflowException ("Value too large or too small."); + return true; + } + } + + // exp value saved as negative + if(!negative) + exp = -exp; + + exc = null; + exponent = (int)exp; + pos = i; + return true; + } + + internal static bool FindOther (ref int pos, string s, string other) + { + if ((pos + other.Length) <= s.Length && + s.Substring (pos, other.Length) == other) { + pos += other.Length; + return true; + } + + return false; + } + + internal static bool ValidDigit (char e, bool allowHex) + { + if (allowHex) + return Char.IsDigit (e) || (e >= 'A' && e <= 'F') || (e >= 'a' && e <= 'f'); + + return Char.IsDigit (e); + } static Exception GetFormatException () { diff --git a/mcs/class/System.Numerics/Test/System.Numerics/BigIntegerTest.cs b/mcs/class/System.Numerics/Test/System.Numerics/BigIntegerTest.cs index 098c50854b7..17393554c98 100644 --- a/mcs/class/System.Numerics/Test/System.Numerics/BigIntegerTest.cs +++ b/mcs/class/System.Numerics/Test/System.Numerics/BigIntegerTest.cs @@ -49,6 +49,29 @@ namespace MonoTests.System.Numerics huge_add }; + private NumberFormatInfo Nfi = NumberFormatInfo.InvariantInfo; + private NumberFormatInfo NfiUser; + + [TestFixtureSetUp] + public void SetUpFixture() + { + NfiUser = new NumberFormatInfo (); + NfiUser.CurrencyDecimalDigits = 3; + NfiUser.CurrencyDecimalSeparator = ":"; + NfiUser.CurrencyGroupSeparator = "/"; + NfiUser.CurrencyGroupSizes = new int[] { 2, 1, 0 }; + NfiUser.CurrencyNegativePattern = 10; // n $- + NfiUser.CurrencyPositivePattern = 3; // n $ + NfiUser.CurrencySymbol = "XYZ"; + NfiUser.PercentDecimalDigits = 1; + NfiUser.PercentDecimalSeparator = ";"; + NfiUser.PercentGroupSeparator = "~"; + NfiUser.PercentGroupSizes = new int[] { 1 }; + NfiUser.PercentNegativePattern = 2; + NfiUser.PercentPositivePattern = 2; + NfiUser.PercentSymbol = "%%%"; + } + [Test] public void Mul () { long[] values = new long [] { -1000000000L, -1000, -1, 0, 1, 1000, 100000000L }; @@ -871,6 +894,37 @@ namespace MonoTests.System.Numerics Assert.AreEqual (10, (int)BigInteger.Parse("+10"), "#7"); Assert.AreEqual (10, (int)BigInteger.Parse("10 "), "#8"); Assert.AreEqual (-10, (int)BigInteger.Parse("-10 "), "#9"); + Assert.AreEqual (10, (int)BigInteger.Parse(" 10 "), "#10"); + Assert.AreEqual (-10, (int)BigInteger.Parse(" -10 "), "#11"); + + Assert.AreEqual (-1, (int)BigInteger.Parse("F", NumberStyles.AllowHexSpecifier), "#12"); + Assert.AreEqual (-8, (int)BigInteger.Parse("8", NumberStyles.AllowHexSpecifier), "#13"); + Assert.AreEqual (8, (int)BigInteger.Parse("08", NumberStyles.AllowHexSpecifier), "#14"); + Assert.AreEqual (15, (int)BigInteger.Parse("0F", NumberStyles.AllowHexSpecifier), "#15"); + Assert.AreEqual (-1, (int)BigInteger.Parse("FF", NumberStyles.AllowHexSpecifier), "#16"); + Assert.AreEqual (255, (int)BigInteger.Parse("0FF", NumberStyles.AllowHexSpecifier), "#17"); + + Assert.AreEqual (-17, (int)BigInteger.Parse(" (17) ", NumberStyles.AllowParentheses | NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite), "#18"); + Assert.AreEqual (-23, (int)BigInteger.Parse(" -23 ", NumberStyles.AllowLeadingSign | NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite), "#19"); + + Assert.AreEqual (300000, (int)BigInteger.Parse("3E5", NumberStyles.AllowExponent), "#20"); + Assert.AreEqual (250, (int)BigInteger.Parse("2"+Nfi.NumberDecimalSeparator+"5E2", NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint), "#21");//2.5E2 = 250 + Assert.AreEqual (25, (int)BigInteger.Parse("2500E-2", NumberStyles.AllowExponent), "#22"); + + Assert.AreEqual ("136236974127783066520110477975349088954559032721408", BigInteger.Parse("136236974127783066520110477975349088954559032721408", NumberStyles.None).ToString(), "#23"); + Assert.AreEqual ("136236974127783066520110477975349088954559032721408", BigInteger.Parse("136236974127783066520110477975349088954559032721408").ToString(), "#24"); + + try { + BigInteger.Parse ("2E3.0", NumberStyles.AllowExponent); // decimal notation for the exponent + Assert.Fail ("#25"); + } catch (FormatException) { + } + + try { + Int32.Parse ("2.09E1", NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent); + Assert.Fail ("#26"); + } catch (OverflowException) { + } } [Test] @@ -892,6 +946,54 @@ namespace MonoTests.System.Numerics Assert.IsTrue (BigInteger.TryParse ("-010", out x), "#11"); Assert.AreEqual (-10, (int)x, "#12"); + + //Number style and format provider + + Assert.IsFalse (BigInteger.TryParse ("null", NumberStyles.None, null, out x), "#13"); + Assert.AreEqual (0, (int)x, "#14"); + Assert.IsFalse (BigInteger.TryParse ("-10", NumberStyles.None, null, out x), "#15"); + Assert.IsFalse (BigInteger.TryParse ("(10)", NumberStyles.None, null, out x), "#16"); + Assert.IsFalse (BigInteger.TryParse (" 10", NumberStyles.None, null, out x), "#17"); + Assert.IsFalse (BigInteger.TryParse ("10 ", NumberStyles.None, null, out x), "#18"); + + Assert.IsTrue (BigInteger.TryParse ("-10", NumberStyles.AllowLeadingSign, null, out x), "#19"); + Assert.AreEqual (-10, (int)x, "#20"); + Assert.IsTrue (BigInteger.TryParse ("(10)", NumberStyles.AllowParentheses, null, out x), "#21"); + Assert.AreEqual (-10, (int)x, "#22"); + Assert.IsTrue (BigInteger.TryParse (" 10", NumberStyles.AllowLeadingWhite, null, out x), "#23"); + Assert.AreEqual (10, (int)x, "#24"); + Assert.IsTrue (BigInteger.TryParse ("10 ", NumberStyles.AllowTrailingWhite, null, out x), "#25"); + Assert.AreEqual (10, (int)x, "#26"); + + Assert.IsFalse (BigInteger.TryParse ("$10", NumberStyles.None, null, out x), "#26"); + Assert.IsFalse (BigInteger.TryParse ("$10", NumberStyles.None, Nfi, out x), "#27"); + Assert.IsFalse (BigInteger.TryParse ("%10", NumberStyles.None, Nfi, out x), "#28"); + Assert.IsFalse (BigInteger.TryParse ("10 ", NumberStyles.None, null, out x), "#29"); + + Assert.IsTrue (BigInteger.TryParse ("10", NumberStyles.None, null, out x), "#30"); + Assert.AreEqual (10, (int)x, "#31"); + Assert.IsTrue (BigInteger.TryParse (Nfi.CurrencySymbol + "10", NumberStyles.AllowCurrencySymbol, Nfi, out x), "#32"); + Assert.AreEqual (10, (int)x, "#33"); + Assert.IsFalse (BigInteger.TryParse ("%10", NumberStyles.AllowCurrencySymbol, Nfi, out x), "#34"); + } + + [Test] + public void TestUserCurrency () + { + const int val1 = -1234567; + const int val2 = 1234567; + + string s = ""; + BigInteger v; + s = val1.ToString ("c", NfiUser); + Assert.AreEqual ("1234/5/67:000 XYZ-", s, "Currency value type 1 is not what we want to try to parse"); + v = BigInteger.Parse ("1234/5/67:000 XYZ-", NumberStyles.Currency, NfiUser); + Assert.AreEqual (val1, (int)v); + + s = val2.ToString ("c", NfiUser); + Assert.AreEqual ("1234/5/67:000 XYZ", s, "Currency value type 2 is not what we want to try to parse"); + v = BigInteger.Parse (s, NumberStyles.Currency, NfiUser); + Assert.AreEqual (val2, (int)v); } [Test]