diff --git a/src/main/java/com/thealgorithms/randomized/MillerRabinPrimality.java b/src/main/java/com/thealgorithms/randomized/MillerRabinPrimality.java new file mode 100644 index 000000000000..5642cc0f0eb1 --- /dev/null +++ b/src/main/java/com/thealgorithms/randomized/MillerRabinPrimality.java @@ -0,0 +1,103 @@ +package com.thealgorithms.randomized; + +import java.math.BigInteger; +import java.util.Random; + +/** + * The Miller–Rabin primality test + * + * Use case: + * + * - Determine whether a number is probably prime or definitely composite. + * + * In cryptography very big prime numbers are required. + * A popular procedure to generate a prime number with n bits is to generate a random number and check its primality. + * This is repeated until a prime number is found. + * + * Time Complexity: O(k log n) + * Space Complexity: O(k) + * where k - number of iterations, i.e. number of random primes to test on. + * + * + * @author DomTr (https://github.com/DomTr) + * @see Miller Rabin primality test - Wikipedia + * + */ +public final class MillerRabinPrimality { + private static final BigInteger ZERO = BigInteger.ZERO; + private static final BigInteger ONE = BigInteger.ONE; + private static final BigInteger TWO = new BigInteger("2"); + private static final BigInteger THREE = new BigInteger("3"); + private static final BigInteger FOUR = new BigInteger("4"); + private static final Random rand = new Random(); + private MillerRabinPrimality() { + throw new UnsupportedOperationException("Utility class"); + } + /** + * Performs the Miller-Rabin probabilistic primality test on the given number. + * + * @param n the number to test for primality + * @return true if n is probably prime, false if it is composite. + * The test never falsely classifies a prime as composite (no false negatives), + * but it can mistakenly identify a composite as probably prime (false positives), + * although this probability is very low and decreases exponentially with the number of bases tested. + */ + public static boolean millerRabin(BigInteger n, int iter) { + if (n.compareTo(ONE) <= 0 || n.equals(FOUR)) return false; + if (n.equals(THREE) || n.equals(TWO)) return true; + long deg = 0; + BigInteger oddPart = n.subtract(ONE); + while (oddPart.mod(TWO).equals(ZERO)) { + oddPart = oddPart.divide(TWO); + deg++; + } + + while (iter-- > 0) { + if (checkComposite(n, oddPart, deg)) { + return false; + } + } + return true; + } + + /** + * Checks whether the given base 'a' is a witness to the compositeness of 'n' + * in the Miller-Rabin primality test. + * + * @param n the number being tested for primality + * @param oddPart the odd part of n-1 (i.e., n - 1 = 2^deg * oddPart) + * @param deg the exponent of 2 in the factorization of n-1 + * @return true if 'a' is a witness that 'n' is composite; + * e false if 'n' might still be prime with respect to this base + */ + public static boolean checkComposite(BigInteger n, BigInteger oddPart, long deg) { + BigInteger a = getRandom(TWO, n.subtract(TWO)); + BigInteger x = a.modPow(oddPart, n); + if (x.equals(n.subtract(ONE)) || x.equals(ONE)) { + return false; + } + long tmpDeg = 1; + while (tmpDeg < deg) { + x = x.modPow(BigInteger.valueOf(2), n); + tmpDeg++; + if (x.equals(n.subtract(ONE))) { + return false; + } + } + return true; + } + /* + * Returns a random BigInteger in [from, to) interval + * + * @param from - lowest value + * @param to - highest value + */ + private static BigInteger getRandom(BigInteger from, BigInteger to) { + BigInteger res; + do { + res = new BigInteger(from.bitLength(), rand); + } while (res.compareTo(from) < 0 || res.compareTo(to) >= 0); + + return res; + } +} diff --git a/src/test/java/com/thealgorithms/randomized/MillerRabinPrimalityTest.java b/src/test/java/com/thealgorithms/randomized/MillerRabinPrimalityTest.java new file mode 100644 index 000000000000..f3d1f0f958da --- /dev/null +++ b/src/test/java/com/thealgorithms/randomized/MillerRabinPrimalityTest.java @@ -0,0 +1,74 @@ +package com.thealgorithms.randomized; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.math.BigInteger; +import org.junit.jupiter.api.Test; + +/* + * Tests for MillerRabinPrimality + * @author DomTr (https://github.com/DomTr) + */ + +public class MillerRabinPrimalityTest { + static final int iter = 10; + + @Test + public void testComposites() { + long[] values = {1, 25, 2932021007403L, 4501680375506332L, 6910906992394051L, 4887521073398877L, 5577943644815725L, 6085993686552764L}; + + for (long v : values) { + BigInteger val = BigInteger.valueOf(v); + assertFalse(MillerRabinPrimality.millerRabin(val, iter)); + } + } + @Test + public void testPrimes() { + long[] values = {2, 17, 137, 317, 405857, 2932031007403L, 6333369275038567L}; + for (long v : values) { + BigInteger val = BigInteger.valueOf(v); + assertTrue(MillerRabinPrimality.millerRabin(val, iter)); + } + } + + // Test all primes + @Test + public void testBigPrimes() { + BigInteger b1 = new BigInteger("423726770669791241889982933129"); + BigInteger b2 = new BigInteger("728801495170617624430641064729"); + BigInteger b3 = new BigInteger("715069831641887124233793734953"); + BigInteger b4 = new BigInteger("214668004859466264786404914307"); + BigInteger b5 = new BigInteger("107280976690907382021651905569"); + BigInteger b6 = new BigInteger("194139053422804244228680212551"); + BigInteger b7 = new BigInteger("225220037755960690862092087151"); + assertTrue(MillerRabinPrimality.millerRabin(b1, iter)); + assertTrue(MillerRabinPrimality.millerRabin(b2, iter)); + assertTrue(MillerRabinPrimality.millerRabin(b3, iter)); + assertTrue(MillerRabinPrimality.millerRabin(b4, iter)); + assertTrue(MillerRabinPrimality.millerRabin(b5, iter)); + assertTrue(MillerRabinPrimality.millerRabin(b6, iter)); + assertTrue(MillerRabinPrimality.millerRabin(b7, iter)); + } + // Tests composite numbers which are products of two big primes + @Test + public void testBigComposites() { + BigInteger p1 = new BigInteger("995224294347733"); // all 4 primes + BigInteger p2 = new BigInteger("990601545052177"); + BigInteger p3 = new BigInteger("924286031819653"); + BigInteger p4 = new BigInteger("408464000499539"); + + assertFalse(MillerRabinPrimality.millerRabin(p1.multiply(p1), iter)); + assertFalse(MillerRabinPrimality.millerRabin(p1.multiply(p2), iter)); + assertFalse(MillerRabinPrimality.millerRabin(p1.multiply(p3), iter)); + assertFalse(MillerRabinPrimality.millerRabin(p1.multiply(p4), iter)); + + assertFalse(MillerRabinPrimality.millerRabin(p2.multiply(p2), iter)); + assertFalse(MillerRabinPrimality.millerRabin(p2.multiply(p3), iter)); + assertFalse(MillerRabinPrimality.millerRabin(p2.multiply(p4), iter)); + + assertFalse(MillerRabinPrimality.millerRabin(p3.multiply(p3), iter)); + assertFalse(MillerRabinPrimality.millerRabin(p3.multiply(p4), iter)); + + assertFalse(MillerRabinPrimality.millerRabin(p4.multiply(p4), iter)); + } +}