From 6ed18ee78ae4b09c6dbd6b8ee869813ea48aea75 Mon Sep 17 00:00:00 2001 From: Christoph Ruegg Date: Thu, 6 May 2010 12:58:55 +0200 Subject: [PATCH] Number Theory: Gcd/Egcd/Lcm for BigInteger --- .../NumberTheory/IntegerTheory.Euclid.Big.cs | 193 ++++++++++++++++++ .../NumberTheory/IntegerTheory.Euclid.cs | 10 +- src/Numerics/NumberTheory/IntegerTheory.cs | 5 +- src/Numerics/Numerics.csproj | 1 + .../NumberTheoryTests/GcdRelatedTest.cs | 8 +- .../GcdRelatedTestBigInteger.cs | 173 ++++++++++++++++ .../NumberTheoryTests/IntegerTheoryTest.cs | 1 + src/UnitTests/UnitTests.csproj | 1 + 8 files changed, 381 insertions(+), 11 deletions(-) create mode 100644 src/Numerics/NumberTheory/IntegerTheory.Euclid.Big.cs create mode 100644 src/UnitTests/NumberTheoryTests/GcdRelatedTestBigInteger.cs diff --git a/src/Numerics/NumberTheory/IntegerTheory.Euclid.Big.cs b/src/Numerics/NumberTheory/IntegerTheory.Euclid.Big.cs new file mode 100644 index 00000000..2eb163dc --- /dev/null +++ b/src/Numerics/NumberTheory/IntegerTheory.Euclid.Big.cs @@ -0,0 +1,193 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://mathnet.opensourcedotnet.info +// +// Copyright (c) 2009-2010 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.NumberTheory +{ + using System; + using System.Collections.Generic; + using System.Numerics; + + /// + /// Number theory utility functions for integers. + /// + public static partial class IntegerTheory + { + /// + /// Returns the greatest common divisor (gcd) of two big integers. + /// + /// First Integer: a. + /// Second Integer: b. + /// Greatest common divisor gcd(a,b) + public static BigInteger GreatestCommonDivisor(BigInteger a, BigInteger b) + { + return BigInteger.GreatestCommonDivisor(a, b); + } + + /// + /// Returns the greatest common divisor (gcd) of a set of big integers. + /// + /// List of Integers. + /// Greatest common divisor gcd(list of integers) + public static BigInteger GreatestCommonDivisor(IList integers) + { + if (null == integers) + { + throw new ArgumentNullException("integers"); + } + + if (integers.Count == 0) + { + return 0; + } + + var gcd = BigInteger.Abs(integers[0]); + + for (int i = 1; (i < integers.Count) && (gcd > BigInteger.One); i++) + { + gcd = GreatestCommonDivisor(gcd, integers[i]); + } + + return gcd; + } + + /// + /// Returns the greatest common divisor (gcd) of a set of big integers. + /// + /// List of Integers. + /// Greatest common divisor gcd(list of integers) + public static BigInteger GreatestCommonDivisor(params BigInteger[] integers) + { + return GreatestCommonDivisor((IList)integers); + } + + /// + /// Computes the extended greatest common divisor, such that a*x + b*y = gcd(a,b). + /// + /// First Integer: a. + /// Second Integer: b. + /// Resulting x, such that a*x + b*y = gcd(a,b). + /// Resulting y, such that a*x + b*y = gcd(a,b) + /// Greatest common divisor gcd(a,b) + /// + /// + /// long x,y,d; + /// d = Fn.GreatestCommonDivisor(45,18,out x, out y); + /// -> d == 9 && x == 1 && y == -2 + /// + /// The gcd of 45 and 18 is 9: 18 = 2*9, 45 = 5*9. 9 = 1*45 -2*18, therefore x=1 and y=-2. + /// + public static BigInteger ExtendedGreatestCommonDivisor( + BigInteger a, + BigInteger b, + out BigInteger x, + out BigInteger y) + { + BigInteger mp = BigInteger.One, np = BigInteger.Zero, m = BigInteger.Zero, n = BigInteger.One; + + while (!b.IsZero) + { + BigInteger rem; + BigInteger quot = BigInteger.DivRem(a, b, out rem); + a = b; + b = rem; + + BigInteger tmp = m; + m = mp - (quot * m); + mp = tmp; + + tmp = n; + n = np - (quot * n); + np = tmp; + } + + if (a >= BigInteger.Zero) + { + x = mp; + y = np; + return a; + } + + x = -mp; + y = -np; + return -a; + } + + /// + /// Returns the least common multiple (lcm) of two big integers. + /// + /// First Integer: a. + /// Second Integer: b. + /// Least common multiple lcm(a,b) + public static BigInteger LeastCommonMultiple(BigInteger a, BigInteger b) + { + if (a.IsZero || b.IsZero) + { + return BigInteger.Zero; + } + + return BigInteger.Abs((a / BigInteger.GreatestCommonDivisor(a, b)) * b); + } + + /// + /// Returns the least common multiple (lcm) of a set of big integers. + /// + /// List of Integers. + /// Least common multiple lcm(list of integers) + public static BigInteger LeastCommonMultiple(IList integers) + { + if (null == integers) + { + throw new ArgumentNullException("integers"); + } + + if (integers.Count == 0) + { + return 1; + } + + var lcm = BigInteger.Abs(integers[0]); + + for (int i = 1; i < integers.Count; i++) + { + lcm = LeastCommonMultiple(lcm, integers[i]); + } + + return lcm; + } + + /// + /// Returns the least common multiple (lcm) of a set of big integers. + /// + /// List of Integers. + /// Least common multiple lcm(list of integers) + public static BigInteger LeastCommonMultiple(params BigInteger[] integers) + { + return LeastCommonMultiple((IList)integers); + } + } +} diff --git a/src/Numerics/NumberTheory/IntegerTheory.Euclid.cs b/src/Numerics/NumberTheory/IntegerTheory.Euclid.cs index c0f8a191..30077c2d 100644 --- a/src/Numerics/NumberTheory/IntegerTheory.Euclid.cs +++ b/src/Numerics/NumberTheory/IntegerTheory.Euclid.cs @@ -2,7 +2,7 @@ // Math.NET Numerics, part of the Math.NET Project // http://mathnet.opensourcedotnet.info // -// Copyright (c) 2009 Math.NET +// Copyright (c) 2009-2010 Math.NET // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation @@ -72,7 +72,7 @@ namespace MathNet.Numerics.NumberTheory return 0; } - long gcd = Math.Abs(integers[0]); + var gcd = Math.Abs(integers[0]); for (int i = 1; (i < integers.Count) && (gcd > 1); i++) { @@ -118,8 +118,8 @@ namespace MathNet.Numerics.NumberTheory while (b != 0) { - long quot = a / b; - long rem = a % b; + long rem; + long quot = Math.DivRem(a, b, out rem); a = b; b = rem; @@ -177,7 +177,7 @@ namespace MathNet.Numerics.NumberTheory return 1; } - long lcm = Math.Abs(integers[0]); + var lcm = Math.Abs(integers[0]); for (int i = 1; i < integers.Count; i++) { diff --git a/src/Numerics/NumberTheory/IntegerTheory.cs b/src/Numerics/NumberTheory/IntegerTheory.cs index d00a2543..c1d058fe 100644 --- a/src/Numerics/NumberTheory/IntegerTheory.cs +++ b/src/Numerics/NumberTheory/IntegerTheory.cs @@ -2,7 +2,7 @@ // Math.NET Numerics, part of the Math.NET Project // http://mathnet.opensourcedotnet.info // -// Copyright (c) 2009 Math.NET +// Copyright (c) 2009-2010 Math.NET // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation @@ -29,8 +29,9 @@ namespace MathNet.Numerics.NumberTheory { using System; + using System.Numerics; - /// + /// /// Number theory utility functions for integers. /// public static partial class IntegerTheory diff --git a/src/Numerics/Numerics.csproj b/src/Numerics/Numerics.csproj index cd2e7f9c..17b808ed 100644 --- a/src/Numerics/Numerics.csproj +++ b/src/Numerics/Numerics.csproj @@ -144,6 +144,7 @@ + diff --git a/src/UnitTests/NumberTheoryTests/GcdRelatedTest.cs b/src/UnitTests/NumberTheoryTests/GcdRelatedTest.cs index caad8985..2faeaace 100644 --- a/src/UnitTests/NumberTheoryTests/GcdRelatedTest.cs +++ b/src/UnitTests/NumberTheoryTests/GcdRelatedTest.cs @@ -92,7 +92,7 @@ namespace MathNet.Numerics.UnitTests.NumberTheoryTests [Test] public void ListGcdHandlesSpecialInputCorrectly() { - Assert.AreEqual(0, IntegerTheory.GreatestCommonDivisor(), "Gcd()"); + Assert.AreEqual(0, IntegerTheory.GreatestCommonDivisor(new long[0]), "Gcd()"); Assert.AreEqual(100, IntegerTheory.GreatestCommonDivisor(-100), "Gcd(-100)"); } @@ -101,7 +101,7 @@ namespace MathNet.Numerics.UnitTests.NumberTheoryTests { Assert.Throws( typeof (ArgumentNullException), - () => IntegerTheory.GreatestCommonDivisor(null)); + () => IntegerTheory.GreatestCommonDivisor((long[])null)); } [Test] @@ -147,7 +147,7 @@ namespace MathNet.Numerics.UnitTests.NumberTheoryTests [Test] public void ListLcmHandlesSpecialInputCorrectly() { - Assert.AreEqual(1, IntegerTheory.LeastCommonMultiple(), "Lcm()"); + Assert.AreEqual(1, IntegerTheory.LeastCommonMultiple(new long[0]), "Lcm()"); Assert.AreEqual(100, IntegerTheory.LeastCommonMultiple(-100), "Lcm(-100)"); } @@ -156,7 +156,7 @@ namespace MathNet.Numerics.UnitTests.NumberTheoryTests { Assert.Throws( typeof(ArgumentNullException), - () => IntegerTheory.LeastCommonMultiple(null)); + () => IntegerTheory.LeastCommonMultiple((long[])null)); } } } diff --git a/src/UnitTests/NumberTheoryTests/GcdRelatedTestBigInteger.cs b/src/UnitTests/NumberTheoryTests/GcdRelatedTestBigInteger.cs new file mode 100644 index 00000000..3d4ff19f --- /dev/null +++ b/src/UnitTests/NumberTheoryTests/GcdRelatedTestBigInteger.cs @@ -0,0 +1,173 @@ +// +// 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.NumberTheoryTests +{ + using System; + using System.Numerics; + using MbUnit.Framework; + using NumberTheory; + + [TestFixture] + public class GcdRelatedTestBigInteger + { + [Test] + public void GcdHandlesNormalInputCorrectly() + { + Assert.AreEqual(0, IntegerTheory.GreatestCommonDivisor(BigInteger.Zero, BigInteger.Zero), "Gcd(0,0)"); + Assert.AreEqual(6, IntegerTheory.GreatestCommonDivisor(BigInteger.Zero, 6), "Gcd(0,6)"); + Assert.AreEqual(1, IntegerTheory.GreatestCommonDivisor((BigInteger)7, 13), "Gcd(7,13)"); + Assert.AreEqual(7, IntegerTheory.GreatestCommonDivisor((BigInteger)7, 14), "Gcd(7,14)"); + Assert.AreEqual(1, IntegerTheory.GreatestCommonDivisor((BigInteger)7, 15), "Gcd(7,15)"); + Assert.AreEqual(3, IntegerTheory.GreatestCommonDivisor((BigInteger)6, 15), "Gcd(6,15)"); + } + + [Test] + public void GcdHandlesNegativeInputCorrectly() + { + Assert.AreEqual(5, IntegerTheory.GreatestCommonDivisor((BigInteger)(-5), 0), "Gcd(-5,0)"); + Assert.AreEqual(5, IntegerTheory.GreatestCommonDivisor(BigInteger.Zero, -5), "Gcd(0, -5)"); + Assert.AreEqual(1, IntegerTheory.GreatestCommonDivisor((BigInteger)(-7), 15), "Gcd(-7,15)"); + Assert.AreEqual(1, IntegerTheory.GreatestCommonDivisor((BigInteger)(-7), -15), "Gcd(-7,-15)"); + } + + [Test] + public void GcdSupportsLargeInput() + { + Assert.AreEqual(Int32.MaxValue, IntegerTheory.GreatestCommonDivisor(BigInteger.Zero, Int32.MaxValue), "Gcd(0,Int32Max)"); + Assert.AreEqual(Int64.MaxValue, IntegerTheory.GreatestCommonDivisor(BigInteger.Zero, Int64.MaxValue), "Gcd(0,Int64Max)"); + Assert.AreEqual(1, IntegerTheory.GreatestCommonDivisor((BigInteger)Int32.MaxValue, Int64.MaxValue), "Gcd(Int32Max,Int64Max)"); + Assert.AreEqual(1 << 18, IntegerTheory.GreatestCommonDivisor((BigInteger)(1 << 18), 1 << 20), "Gcd(1>>18,1<<20)"); + Assert.AreEqual(1 << 18, IntegerTheory.GreatestCommonDivisor((BigInteger)(1 << 18), 1 << 20), "Gcd(1>>18,1<<20)"); + Assert.AreEqual(4569031055798, IntegerTheory.GreatestCommonDivisor(BigInteger.Parse("7305316061155559483748611586449542122662"), BigInteger.Parse("57377277362010117405715236427413896")), "Gcd(large)"); + } + + [Test] + public void ExtendedGcdHandlesNormalInputCorrectly() + { + BigInteger x, y; + + Assert.AreEqual(3, IntegerTheory.ExtendedGreatestCommonDivisor((BigInteger)6, 15, out x, out y), "Egcd(6,15)"); + Assert.AreEqual(3, 6 * x + 15 * y, "Egcd(6,15) -> a*x+b*y"); + + Assert.AreEqual(3, IntegerTheory.ExtendedGreatestCommonDivisor((BigInteger)(-6), 15, out x, out y), "Egcd(-6,15)"); + Assert.AreEqual(3, -6 * x + 15 * y, "Egcd(-6,15) -> a*x+b*y"); + + Assert.AreEqual(3, IntegerTheory.ExtendedGreatestCommonDivisor((BigInteger)(-6), -15, out x, out y), "Egcd(-6,-15)"); + Assert.AreEqual(3, -6 * x + -15 * y, "Egcd(-6,-15) -> a*x+b*y"); + + var a = BigInteger.Parse("7305316061155559483748611586449542122662"); + var b = BigInteger.Parse("57377277362010117405715236427413896"); + Assert.AreEqual(4569031055798, IntegerTheory.ExtendedGreatestCommonDivisor(a, b, out x, out y), "Egcd(large)"); + Assert.AreEqual(4569031055798, a * x + b * y, "Egcd(large) -> a*x+b*y"); + Assert.AreEqual(4569031055798, IntegerTheory.ExtendedGreatestCommonDivisor(-a, b, out x, out y), "Egcd(-large)"); + Assert.AreEqual(4569031055798, -a * x + b * y, "Egcd(-large) -> a*x+b*y"); + } + + [Test] + public void ListGcdHandlesNormalInputCorrectly() + { + Assert.AreEqual(2, IntegerTheory.GreatestCommonDivisor((BigInteger)(-10), 6, -8), "Gcd(-10,6,-8)"); + Assert.AreEqual(1, IntegerTheory.GreatestCommonDivisor((BigInteger)(-10), 6, -8, 5, 9, 13), "Gcd(-10,6,-8,5,9,13)"); + Assert.AreEqual(5, IntegerTheory.GreatestCommonDivisor((BigInteger)(-10), 20, 120, 60, -15, 1000), "Gcd(-10,20,120,60,-15,1000)"); + Assert.AreEqual(3, IntegerTheory.GreatestCommonDivisor((BigInteger)(Int64.MaxValue - 1), Int64.MaxValue - 4, Int64.MaxValue - 7), "Gcd(Int64Max-1,Int64Max-4,Int64Max-7)"); + Assert.AreEqual(123, IntegerTheory.GreatestCommonDivisor((BigInteger)492, -2 * 492, 492 / 4), "Gcd(492, -984, 123)"); + } + + [Test] + public void ListGcdHandlesSpecialInputCorrectly() + { + Assert.AreEqual(0, IntegerTheory.GreatestCommonDivisor(new BigInteger[0]), "Gcd()"); + Assert.AreEqual(100, IntegerTheory.GreatestCommonDivisor((BigInteger)(-100)), "Gcd(-100)"); + } + + [Test] + public void ListGcdChecksForNullArguments() + { + Assert.Throws( + typeof (ArgumentNullException), + () => IntegerTheory.GreatestCommonDivisor((BigInteger[])null)); + } + + [Test] + public void LcmHandlesNormalInputCorrectly() + { + Assert.AreEqual(10, IntegerTheory.LeastCommonMultiple((BigInteger)10, 10), "Lcm(10,10)"); + + Assert.AreEqual(0, IntegerTheory.LeastCommonMultiple(BigInteger.Zero, 10), "Lcm(0,10)"); + Assert.AreEqual(0, IntegerTheory.LeastCommonMultiple((BigInteger)10, 0), "Lcm(10,0)"); + + Assert.AreEqual(77, IntegerTheory.LeastCommonMultiple((BigInteger)11, 7), "Lcm(11,7)"); + Assert.AreEqual(33, IntegerTheory.LeastCommonMultiple((BigInteger)11, 33), "Lcm(11,33)"); + Assert.AreEqual(374, IntegerTheory.LeastCommonMultiple((BigInteger)11, 34), "Lcm(11,34)"); + } + + [Test] + public void LcmHandlesNegativeInputCorrectly() + { + Assert.AreEqual(352, IntegerTheory.LeastCommonMultiple((BigInteger)11, -32), "Lcm(11,-32)"); + Assert.AreEqual(352, IntegerTheory.LeastCommonMultiple((BigInteger)(-11), 32), "Lcm(-11,32)"); + Assert.AreEqual(352, IntegerTheory.LeastCommonMultiple((BigInteger)(-11), -32), "Lcm(-11,-32)"); + } + + [Test] + public void LcmSupportsLargeInput() + { + Assert.AreEqual(Int32.MaxValue, IntegerTheory.LeastCommonMultiple((BigInteger)Int32.MaxValue, Int32.MaxValue), "Lcm(Int32Max,Int32Max)"); + Assert.AreEqual(Int64.MaxValue, IntegerTheory.LeastCommonMultiple((BigInteger)Int64.MaxValue, Int64.MaxValue), "Lcm(Int64Max,Int64Max)"); + Assert.AreEqual(Int64.MaxValue, IntegerTheory.LeastCommonMultiple((BigInteger)(-Int64.MaxValue), -Int64.MaxValue), "Lcm(-Int64Max,-Int64Max)"); + Assert.AreEqual(Int64.MaxValue, IntegerTheory.LeastCommonMultiple((BigInteger)(-Int64.MaxValue), Int64.MaxValue), "Lcm(-Int64Max,Int64Max)"); + Assert.AreEqual(BigInteger.Parse("91739176367857263082719902034485224119528064014300888465614024"), IntegerTheory.LeastCommonMultiple(BigInteger.Parse("7305316061155559483748611586449542122662"), BigInteger.Parse("57377277362010117405715236427413896")), "Lcm(large)"); + } + + [Test] + public void ListLcmHandlesNormalInputCorrectly() + { + Assert.AreEqual(120, IntegerTheory.LeastCommonMultiple((BigInteger)(-10), 6, -8), "Lcm(-10,6,-8)"); + Assert.AreEqual(4680, IntegerTheory.LeastCommonMultiple((BigInteger)(-10), 6, -8, 5, 9, 13), "Lcm(-10,6,-8,5,9,13)"); + Assert.AreEqual(3000, IntegerTheory.LeastCommonMultiple((BigInteger)(-10), 20, 120, 60, -15, 1000), "Lcm(-10,20,120,60,-15,1000)"); + Assert.AreEqual(984, IntegerTheory.LeastCommonMultiple((BigInteger)492, -2 * 492, 492 / 4), "Lcm(492, -984, 123)"); + Assert.AreEqual(2016, IntegerTheory.LeastCommonMultiple((BigInteger)32, 42, 36, 18), "Lcm(32,42,36,18)"); + } + + [Test] + public void ListLcmHandlesSpecialInputCorrectly() + { + Assert.AreEqual(1, IntegerTheory.LeastCommonMultiple(new BigInteger[0]), "Lcm()"); + Assert.AreEqual(100, IntegerTheory.LeastCommonMultiple((BigInteger)(-100)), "Lcm(-100)"); + } + + [Test] + public void ListLcmChecksForNullArguments() + { + Assert.Throws( + typeof(ArgumentNullException), + () => IntegerTheory.LeastCommonMultiple((BigInteger[])null)); + } + } +} diff --git a/src/UnitTests/NumberTheoryTests/IntegerTheoryTest.cs b/src/UnitTests/NumberTheoryTests/IntegerTheoryTest.cs index 9a81d7b2..d3119b8a 100644 --- a/src/UnitTests/NumberTheoryTests/IntegerTheoryTest.cs +++ b/src/UnitTests/NumberTheoryTests/IntegerTheoryTest.cs @@ -29,6 +29,7 @@ namespace MathNet.Numerics.UnitTests.NumberTheoryTests { using System; + using System.Numerics; using NumberTheory; using MbUnit.Framework; diff --git a/src/UnitTests/UnitTests.csproj b/src/UnitTests/UnitTests.csproj index d1d0c70d..79c193a5 100644 --- a/src/UnitTests/UnitTests.csproj +++ b/src/UnitTests/UnitTests.csproj @@ -128,6 +128,7 @@ +