mirror of
https://github.com/TheAlgorithms/Java.git
synced 2025-07-06 00:54:32 +08:00
feat: add solovay strassen primality test (#5692)
* feat: add solovay strassen primality test * chore: add wikipedia link * fix: format and coverage * fix: mvn stylecheck * fix: PMD errors * refactor: make random final --------- Co-authored-by: Alex Klymenko <alexanderklmn@gmail.com>
This commit is contained in:
@ -0,0 +1,133 @@
|
||||
package com.thealgorithms.maths;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* This class implements the Solovay-Strassen primality test,
|
||||
* which is a probabilistic algorithm to determine whether a number is prime.
|
||||
* The algorithm is based on properties of the Jacobi symbol and modular exponentiation.
|
||||
*
|
||||
* For more information, go to {@link https://en.wikipedia.org/wiki/Solovay%E2%80%93Strassen_primality_test}
|
||||
*/
|
||||
final class SolovayStrassenPrimalityTest {
|
||||
|
||||
private final Random random;
|
||||
|
||||
/**
|
||||
* Constructs a SolovayStrassenPrimalityTest instance with a specified seed for randomness.
|
||||
*
|
||||
* @param seed the seed for generating random numbers
|
||||
*/
|
||||
private SolovayStrassenPrimalityTest(int seed) {
|
||||
random = new Random(seed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create an instance of SolovayStrassenPrimalityTest.
|
||||
*
|
||||
* @param seed the seed for generating random numbers
|
||||
* @return a new instance of SolovayStrassenPrimalityTest
|
||||
*/
|
||||
public static SolovayStrassenPrimalityTest getSolovayStrassenPrimalityTest(int seed) {
|
||||
return new SolovayStrassenPrimalityTest(seed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates modular exponentiation using the method of exponentiation by squaring.
|
||||
*
|
||||
* @param base the base number
|
||||
* @param exponent the exponent
|
||||
* @param mod the modulus
|
||||
* @return (base^exponent) mod mod
|
||||
*/
|
||||
private static long calculateModularExponentiation(long base, long exponent, long mod) {
|
||||
long x = 1; // This will hold the result of (base^exponent) % mod
|
||||
long y = base; // This holds the current base value being squared
|
||||
|
||||
while (exponent > 0) {
|
||||
// If exponent is odd, multiply the current base (y) with x
|
||||
if (exponent % 2 == 1) {
|
||||
x = x * y % mod; // Update result with current base
|
||||
}
|
||||
// Square the base for the next iteration
|
||||
y = y * y % mod; // Update base to be y^2
|
||||
exponent = exponent / 2; // Halve the exponent for next iteration
|
||||
}
|
||||
|
||||
return x % mod; // Return final result after all iterations
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the Jacobi symbol (a/n), which is a generalization of the Legendre symbol.
|
||||
*
|
||||
* @param a the numerator
|
||||
* @param num the denominator (must be an odd positive integer)
|
||||
* @return the Jacobi symbol value: 1, -1, or 0
|
||||
*/
|
||||
public int calculateJacobi(long a, long num) {
|
||||
// Check if num is non-positive or even; Jacobi symbol is not defined in these cases
|
||||
if (num <= 0 || num % 2 == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
a = a % num; // Reduce a modulo num to simplify calculations
|
||||
int jacobi = 1; // Initialize Jacobi symbol value
|
||||
|
||||
while (a != 0) {
|
||||
// While a is even, reduce it and adjust jacobi based on properties of num
|
||||
while (a % 2 == 0) {
|
||||
a /= 2; // Divide a by 2 until it becomes odd
|
||||
long nMod8 = num % 8; // Get num modulo 8 to check conditions for jacobi adjustment
|
||||
if (nMod8 == 3 || nMod8 == 5) {
|
||||
jacobi = -jacobi; // Flip jacobi sign based on properties of num modulo 8
|
||||
}
|
||||
}
|
||||
|
||||
long temp = a; // Temporarily store value of a
|
||||
a = num; // Set a to be num for next iteration
|
||||
num = temp; // Set num to be previous value of a
|
||||
|
||||
// Adjust jacobi based on properties of both numbers when both are odd and congruent to 3 modulo 4
|
||||
if (a % 4 == 3 && num % 4 == 3) {
|
||||
jacobi = -jacobi; // Flip jacobi sign again based on congruences
|
||||
}
|
||||
|
||||
a = a % num; // Reduce a modulo num for next iteration of Jacobi computation
|
||||
}
|
||||
|
||||
return (num == 1) ? jacobi : 0; // If num reduces to 1, return jacobi value, otherwise return 0 (not defined)
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the Solovay-Strassen primality test on a given number.
|
||||
*
|
||||
* @param num the number to be tested for primality
|
||||
* @param iterations the number of iterations to run for accuracy
|
||||
* @return true if num is likely prime, false if it is composite
|
||||
*/
|
||||
public boolean solovayStrassen(long num, int iterations) {
|
||||
if (num <= 1) {
|
||||
return false; // Numbers <=1 are not prime by definition.
|
||||
}
|
||||
if (num <= 3) {
|
||||
return true; // Numbers <=3 are prime.
|
||||
}
|
||||
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
long r = Math.abs(random.nextLong() % (num - 1)) + 2; // Generate a non-negative random number.
|
||||
long a = r % (num - 1) + 1; // Choose random 'a' in range [1, n-1].
|
||||
|
||||
long jacobi = (num + calculateJacobi(a, num)) % num;
|
||||
// Calculate Jacobi symbol and adjust it modulo n.
|
||||
|
||||
long mod = calculateModularExponentiation(a, (num - 1) / 2, num);
|
||||
// Calculate modular exponentiation: a^((n-1)/2) mod n.
|
||||
|
||||
if (jacobi == 0 || mod != jacobi) {
|
||||
return false; // If Jacobi symbol is zero or doesn't match modular result, n is composite.
|
||||
}
|
||||
}
|
||||
|
||||
return true; // If no contradictions found after all iterations, n is likely prime.
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user