diff --git a/src/Neo.Extensions/BigIntegerExtensions.cs b/src/Neo.Extensions/BigIntegerExtensions.cs index 26aee1ae1e..93989c37ca 100644 --- a/src/Neo.Extensions/BigIntegerExtensions.cs +++ b/src/Neo.Extensions/BigIntegerExtensions.cs @@ -177,12 +177,6 @@ public static BigInteger Sqrt(this BigInteger value) return z; } - internal static BigInteger GetLowPart(this BigInteger value, int bitCount) - { - var mask = (BigInteger.One << bitCount) - 1; - return value & mask; - } - /// /// Gets the number of bits required for shortest two's complement representation of the current instance without the sign bit. /// Note: This method is imprecise and might not work as expected with integers larger than 256 bits if less than .NET5. diff --git a/src/Neo.Extensions/Factories/RandomNumberFactory.cs b/src/Neo.Extensions/Factories/RandomNumberFactory.cs index 72e16c739c..ce5e37184a 100644 --- a/src/Neo.Extensions/Factories/RandomNumberFactory.cs +++ b/src/Neo.Extensions/Factories/RandomNumberFactory.cs @@ -176,7 +176,7 @@ public static ulong NextUInt64() public static ulong NextUInt64(ulong maxValue) { - var randomProduct = BigMul(maxValue, NextUInt64(), out var lowPart); + var randomProduct = Math.BigMul(maxValue, NextUInt64(), out var lowPart); if (lowPart < maxValue) { @@ -184,7 +184,7 @@ public static ulong NextUInt64(ulong maxValue) while (lowPart < remainder) { - randomProduct = BigMul(maxValue, NextUInt64(), out lowPart); + randomProduct = Math.BigMul(maxValue, NextUInt64(), out lowPart); } } @@ -216,34 +216,27 @@ public static BigInteger NextBigInteger(BigInteger maxValue) if (maxValue.Sign < 0) throw new ArgumentOutOfRangeException(nameof(maxValue)); + if (maxValue == 0 || maxValue == 1) + return BigInteger.Zero; + var maxValueBits = maxValue.GetByteCount() * 8; - var maxValueSize = BigInteger.Pow(2, maxValueBits); + var maxMaxValue = BigInteger.One << maxValueBits; var randomProduct = maxValue * NextBigInteger(maxValueBits); - var randomProductBits = randomProduct.GetByteCount() * 8; - - var lowPart = randomProduct.GetLowPart(maxValueBits); + var lowPart = randomProduct % maxMaxValue; if (lowPart < maxValue) { - var remainder = (maxValueSize - maxValue) % maxValue; + var threshold = (maxMaxValue - maxValue) % maxValue; - while (lowPart < remainder) + while (lowPart < threshold) { randomProduct = maxValue * NextBigInteger(maxValueBits); - randomProductBits = randomProduct.GetByteCount() * 8; - lowPart = randomProduct.GetLowPart(maxValueBits); + lowPart = randomProduct % maxMaxValue; } } - var result = randomProduct >> (randomProductBits - maxValueBits); - - // Since BigInteger doesn't have a max value or bit size - // anything over 'maxValue' return zero - if (result >= maxValue) - return BigInteger.Zero; - - return result; + return randomProduct >> maxValueBits; } public static BigInteger NextBigInteger(int sizeInBits) @@ -265,29 +258,6 @@ public static BigInteger NextBigInteger(int sizeInBits) return new BigInteger(b); } - private static ulong BigMul(ulong a, ulong b, out ulong low) - { - // Adaptation of algorithm for multiplication - // of 32-bit unsigned integers described - // in Hacker's Delight by Henry S. Warren, Jr. (ISBN 0-201-91465-4), Chapter 8 - // Basically, it's an optimized version of FOIL method applied to - // low and high dwords of each operand - - // Use 32-bit uints to optimize the fallback for 32-bit platforms. - var al = (uint)a; - var ah = (uint)(a >> 32); - var bl = (uint)b; - var bh = (uint)(b >> 32); - - var mull = ((ulong)al) * bl; - var t = ((ulong)ah) * bl + (mull >> 32); - var tl = ((ulong)al) * bh + (uint)t; - - low = (tl << 32) | (uint)mull; - - return ((ulong)ah) * bh + (t >> 32) + (tl >> 32); - } - public static byte[] NextBytes(int length, bool cryptography = false) { ArgumentOutOfRangeException.ThrowIfLessThan(length, 0, nameof(length)); diff --git a/tests/Neo.Extensions.Tests/Factories/UT_RandomNumberFactory.cs b/tests/Neo.Extensions.Tests/Factories/UT_RandomNumberFactory.cs index 9e4525ee6b..e6883fa41e 100644 --- a/tests/Neo.Extensions.Tests/Factories/UT_RandomNumberFactory.cs +++ b/tests/Neo.Extensions.Tests/Factories/UT_RandomNumberFactory.cs @@ -40,10 +40,10 @@ public void CheckNextSByteInRange() Assert.AreEqual(expectedMin, RandomNumberFactory.NextSByte(expectedMin, expectedMin)); var actualValue = RandomNumberFactory.NextSByte(expectedMin, expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= expectedMin && actualValue < expectedMax); actualValue = RandomNumberFactory.NextSByte(expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= 0 && actualValue < expectedMax); } [TestMethod] @@ -53,7 +53,10 @@ public void CheckNextSByteNegative() var expectedMin = sbyte.MinValue; var actualValue = RandomNumberFactory.NextSByte(expectedMin, expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= expectedMin && actualValue < expectedMax); + + actualValue = RandomNumberFactory.NextSByte(expectedMax); + Assert.IsTrue(actualValue >= 0 && actualValue <= expectedMax); } [TestMethod] @@ -73,10 +76,10 @@ public void CheckNextByteInRange() Assert.AreEqual(expectedMin, RandomNumberFactory.NextByte(expectedMin, expectedMin)); var actualValue = RandomNumberFactory.NextByte(expectedMin, expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= expectedMin && actualValue < expectedMax); actualValue = RandomNumberFactory.NextByte(expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= 0 && actualValue < expectedMax); } [TestMethod] @@ -89,10 +92,10 @@ public void CheckNextInt16InRange() Assert.AreEqual(expectedMin, RandomNumberFactory.NextInt16(expectedMin, expectedMin)); var actualValue = RandomNumberFactory.NextInt16(expectedMin, expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= expectedMin && actualValue < expectedMax); actualValue = RandomNumberFactory.NextInt16(expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= 0 && actualValue < expectedMax); } [TestMethod] @@ -102,10 +105,10 @@ public void CheckNextInt16InNegative() var expectedMin = short.MinValue; var actualValue = RandomNumberFactory.NextInt16(expectedMin, expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= expectedMin && actualValue < expectedMax); actualValue = RandomNumberFactory.NextInt16(expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= 0 && actualValue <= expectedMax); } [TestMethod] @@ -125,10 +128,10 @@ public void CheckNextUInt16InRange() Assert.AreEqual(expectedMin, RandomNumberFactory.NextUInt16(expectedMin, expectedMin)); var actualValue = RandomNumberFactory.NextUInt16(expectedMin, expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= expectedMin && actualValue < expectedMax); actualValue = RandomNumberFactory.NextUInt16(expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= 0 && actualValue < expectedMax); } [TestMethod] @@ -141,10 +144,10 @@ public void CheckNextInt32InRange() Assert.AreEqual(expectedMin, RandomNumberFactory.NextInt32(expectedMin, expectedMin)); var actualValue = RandomNumberFactory.NextInt32(expectedMin, expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= expectedMin && actualValue < expectedMax); actualValue = RandomNumberFactory.NextInt32(expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= 0 && actualValue < expectedMax); } [TestMethod] @@ -154,7 +157,7 @@ public void CheckNextInt32InNegative() var expectedMin = int.MinValue; var actualValue = RandomNumberFactory.NextInt32(expectedMin, expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= expectedMin && actualValue < expectedMax); } [TestMethod] @@ -174,10 +177,10 @@ public void CheckNextUInt32InRange() Assert.AreEqual(expectedMin, RandomNumberFactory.NextUInt32(expectedMin, expectedMin)); var actualValue = RandomNumberFactory.NextUInt32(expectedMin, expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= expectedMin && actualValue < expectedMax); actualValue = RandomNumberFactory.NextUInt32(expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= 0 && actualValue < expectedMax); } [TestMethod] @@ -190,10 +193,10 @@ public void CheckNextInt64InRange() Assert.AreEqual(expectedMin, RandomNumberFactory.NextInt64(expectedMin, expectedMin)); var actualValue = RandomNumberFactory.NextInt64(expectedMin, expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= expectedMin && actualValue < expectedMax); actualValue = RandomNumberFactory.NextInt64(expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= 0 && actualValue < expectedMax); } [TestMethod] @@ -203,7 +206,7 @@ public void CheckNextInt64InNegative() var expectedMin = long.MinValue; var actualValue = RandomNumberFactory.NextInt64(expectedMin, expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= expectedMin && actualValue < expectedMax); } [TestMethod] @@ -223,10 +226,10 @@ public void CheckNextUInt64InRange() Assert.AreEqual(expectedMin, RandomNumberFactory.NextUInt64(expectedMin, expectedMin)); var actualValue = RandomNumberFactory.NextUInt64(expectedMin, expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= expectedMin && actualValue < expectedMax); actualValue = RandomNumberFactory.NextUInt64(expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= 0 && actualValue < expectedMax); } [TestMethod] @@ -245,16 +248,16 @@ public void CheckNextBigIntegerSizeInBits() public void CheckNextBigIntegerInRange() { var expectedMax = BigInteger.Pow(2, 100); - var expectedMin = BigInteger.Zero; + var expectedMin = BigInteger.Pow(2, 50); Assert.AreEqual(expectedMax, RandomNumberFactory.NextBigInteger(expectedMax, expectedMax)); Assert.AreEqual(expectedMin, RandomNumberFactory.NextBigInteger(expectedMin, expectedMin)); var actualValue = RandomNumberFactory.NextBigInteger(expectedMin, expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= expectedMin && actualValue < expectedMax); actualValue = RandomNumberFactory.NextBigInteger(expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= 0 && actualValue < expectedMax); } [TestMethod] @@ -264,7 +267,7 @@ public void CheckNextBigIntegerInNegative() var expectedMin = BigInteger.Pow(2, 100) * BigInteger.MinusOne; var actualValue = RandomNumberFactory.NextBigInteger(expectedMin, expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= expectedMin && actualValue < expectedMax); Assert.IsLessThan(0, actualValue.Sign); } @@ -282,21 +285,41 @@ public void CheckNextBigIntegerSmallValues() var expectedMin = BigInteger.Zero; var actualValue = RandomNumberFactory.NextBigInteger(expectedMin, expectedMax); - Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + Assert.IsTrue(actualValue >= expectedMin && actualValue < expectedMax); actualValue = RandomNumberFactory.NextBigInteger(expectedMax); + Assert.IsTrue(actualValue >= 0 && actualValue < expectedMax); + } + + [TestMethod] + public void CheckNextBigIntegerZero() + { + var expectedMax = BigInteger.Zero; + var expectedMin = BigInteger.Zero; + + var actualValue = RandomNumberFactory.NextBigInteger(expectedMin, expectedMax); Assert.IsTrue(actualValue >= expectedMin && actualValue <= expectedMax); + + actualValue = RandomNumberFactory.NextBigInteger(expectedMax); + Assert.IsTrue(actualValue >= 0 && actualValue <= expectedMax); } [TestMethod] public void CheckNextBytes() { - var a = RandomNumberFactory.NextBytes(10); - Assert.AreEqual(10, a.Length); + var notExpectedBytes = new byte[10]; + + var actualBytes1 = RandomNumberFactory.NextBytes(10); + Assert.IsNotEmpty(actualBytes1); + CollectionAssert.AreNotEqual(notExpectedBytes, actualBytes1); + Assert.HasCount(10, actualBytes1); + + var actualBytes2 = RandomNumberFactory.NextBytes(10, cryptography: true); + Assert.IsNotEmpty(actualBytes2); + CollectionAssert.AreNotEqual(notExpectedBytes, actualBytes2); + Assert.HasCount(10, actualBytes2); - var b = RandomNumberFactory.NextBytes(10, cryptography: true); - Assert.AreEqual(10, b.Length); - CollectionAssert.AreNotEqual(a, b); + CollectionAssert.AreNotEqual(actualBytes1, actualBytes2); } } }