Skip to content

Commit 8fc2a55

Browse files
committed
feat: add solovay strassen primality test
1 parent 6535d47 commit 8fc2a55

File tree

2 files changed

+256
-0
lines changed

2 files changed

+256
-0
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package com.thealgorithms.maths;
2+
3+
import java.util.Random;
4+
5+
/**
6+
* This class implements the Solovay-Strassen primality test,
7+
* which is a probabilistic algorithm to determine whether a number is prime.
8+
* The algorithm is based on properties of the Jacobi symbol and modular exponentiation.
9+
*/
10+
class SolovayStrassenPrimalityTest {
11+
12+
private Random random;
13+
14+
/**
15+
* Constructs a SolovayStrassenPrimalityTest instance with a specified seed for randomness.
16+
*
17+
* @param seed the seed for generating random numbers
18+
*/
19+
private SolovayStrassenPrimalityTest(int seed) {
20+
random = new Random(seed);
21+
}
22+
23+
/**
24+
* Factory method to create an instance of SolovayStrassenPrimalityTest.
25+
*
26+
* @param seed the seed for generating random numbers
27+
* @return a new instance of SolovayStrassenPrimalityTest
28+
*/
29+
public static SolovayStrassenPrimalityTest getSolovayStrassenPrimalityTest(int seed) {
30+
return new SolovayStrassenPrimalityTest(seed);
31+
}
32+
33+
/**
34+
* Calculates modular exponentiation using the method of exponentiation by squaring.
35+
*
36+
* @param base the base number
37+
* @param exponent the exponent
38+
* @param mod the modulus
39+
* @return (base^exponent) mod mod
40+
*/
41+
private static long calculateModularExponentiation(long base, long exponent, long mod) {
42+
long x = 1; // This will hold the result of (base^exponent) % mod
43+
long y = base; // This holds the current base value being squared
44+
45+
while (exponent > 0) {
46+
// If exponent is odd, multiply the current base (y) with x
47+
if (exponent % 2 == 1)
48+
x = (x * y) % mod; // Update result with current base
49+
50+
// Square the base for the next iteration
51+
y = (y * y) % mod; // Update base to be y^2
52+
exponent = exponent / 2; // Halve the exponent for next iteration
53+
}
54+
55+
return x % mod; // Return final result after all iterations
56+
}
57+
58+
/**
59+
* Computes the Jacobi symbol (a/n), which is a generalization of the Legendre symbol.
60+
*
61+
* @param a the numerator
62+
* @param num the denominator (must be an odd positive integer)
63+
* @return the Jacobi symbol value: 1, -1, or 0
64+
*/
65+
public int calculateJacobi(long a, long num) {
66+
// Check if num is non-positive or even; Jacobi symbol is not defined in these cases
67+
if (num <= 0 || num % 2 == 0) {
68+
return 0;
69+
}
70+
71+
a = a % num; // Reduce a modulo num to simplify calculations
72+
int jacobi = 1; // Initialize Jacobi symbol value
73+
74+
while (a != 0) {
75+
// While a is even, reduce it and adjust jacobi based on properties of num
76+
while (a % 2 == 0) {
77+
a /= 2; // Divide a by 2 until it becomes odd
78+
long nMod8 = num % 8; // Get num modulo 8 to check conditions for jacobi adjustment
79+
if (nMod8 == 3 || nMod8 == 5) {
80+
jacobi = -jacobi; // Flip jacobi sign based on properties of num modulo 8
81+
}
82+
}
83+
84+
long temp = a; // Temporarily store value of a
85+
a = num; // Set a to be num for next iteration
86+
num = temp; // Set num to be previous value of a
87+
88+
// Adjust jacobi based on properties of both numbers when both are odd and congruent to 3 modulo 4
89+
if (a % 4 == 3 && num % 4 == 3) {
90+
jacobi = -jacobi; // Flip jacobi sign again based on congruences
91+
}
92+
93+
a = a % num; // Reduce a modulo num for next iteration of Jacobi computation
94+
}
95+
96+
return (num == 1) ? jacobi : 0; // If num reduces to 1, return jacobi value, otherwise return 0 (not defined)
97+
}
98+
99+
/**
100+
* Performs the Solovay-Strassen primality test on a given number.
101+
*
102+
* @param num the number to be tested for primality
103+
* @param iterations the number of iterations to run for accuracy
104+
* @return true if num is likely prime, false if it is composite
105+
*/
106+
public boolean solovayStrassen(long num, int iterations) {
107+
if (num <= 1) return false; // Numbers <=1 are not prime by definition.
108+
if (num <= 3) return true; // Numbers <=3 are prime.
109+
110+
for (int i = 0; i < iterations; i++) {
111+
long r = (random.nextLong() & Long.MAX_VALUE); // Generate a non-negative random number.
112+
long a = r % (num - 1) + 1; // Choose random 'a' in range [1, n-1].
113+
114+
long jacobi = (num + calculateJacobi(a, num)) % num;
115+
// Calculate Jacobi symbol and adjust it modulo n.
116+
117+
long mod = calculateModularExponentiation(a, (num - 1) / 2, num);
118+
// Calculate modular exponentiation: a^((n-1)/2) mod n.
119+
120+
if (jacobi == 0 || mod != jacobi) return false;
121+
// If Jacobi symbol is zero or doesn't match modular result, n is composite.
122+
}
123+
124+
return true; // If no contradictions found after all iterations, n is likely prime.
125+
}
126+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package com.thealgorithms.maths;
2+
3+
import org.junit.jupiter.api.BeforeEach;
4+
import org.junit.jupiter.api.Test;
5+
import org.junit.jupiter.params.ParameterizedTest;
6+
import org.junit.jupiter.params.provider.MethodSource;
7+
8+
import static org.junit.jupiter.api.Assertions.*;
9+
10+
/**
11+
* Unit tests for the {@link SolovayStrassenPrimalityTest} class.
12+
* This class tests the functionality of the Solovay-Strassen primality test implementation.
13+
*/
14+
class SolovayStrassenPrimalityTestTest {
15+
16+
private static final int RANDOM_SEED = 123; // Seed for reproducibility
17+
private SolovayStrassenPrimalityTest testInstance;
18+
19+
/**
20+
* Sets up a new instance of {@link SolovayStrassenPrimalityTest}
21+
* before each test case, using a fixed random seed for consistency.
22+
*/
23+
@BeforeEach
24+
void setUp() {
25+
testInstance = SolovayStrassenPrimalityTest.getSolovayStrassenPrimalityTest(RANDOM_SEED);
26+
}
27+
28+
/**
29+
* Provides test cases for prime numbers with various values of n and k (iterations).
30+
*
31+
* @return an array of objects containing pairs of n and k values
32+
*/
33+
static Object[][] primeNumbers() {
34+
return new Object[][]{
35+
{2, 1}, {3, 1}, {5, 5}, {7, 10},
36+
{11, 20}, {13, 10}, {17, 5}, {19, 1}
37+
};
38+
}
39+
40+
/**
41+
* Tests known prime numbers with various values of n and k (iterations).
42+
*
43+
* @param n the number to be tested for primality
44+
* @param k the number of iterations to use in the primality test
45+
*/
46+
@ParameterizedTest
47+
@MethodSource("primeNumbers")
48+
void testPrimeNumbersWithDifferentNAndK(int n, int k) {
49+
assertTrue(testInstance.solovayStrassen(n, k), n + " should be prime");
50+
}
51+
52+
/**
53+
* Provides test cases for composite numbers with various values of n and k (iterations).
54+
*
55+
* @return an array of objects containing pairs of n and k values
56+
*/
57+
static Object[][] compositeNumbers() {
58+
return new Object[][]{
59+
{4, 1}, {6, 5}, {8, 10}, {9, 20},
60+
{10, 1}, {12, 5}, {15, 10}
61+
};
62+
}
63+
64+
/**
65+
* Tests known composite numbers with various values of n and k (iterations).
66+
*
67+
* @param n the number to be tested for primality
68+
* @param k the number of iterations to use in the primality test
69+
*/
70+
@ParameterizedTest
71+
@MethodSource("compositeNumbers")
72+
void testCompositeNumbersWithDifferentNAndK(int n, int k) {
73+
assertFalse(testInstance.solovayStrassen(n, k), n + " should be composite");
74+
}
75+
76+
/**
77+
* Tests edge cases for the primality test.
78+
* This includes negative numbers and small integers (0 and 1).
79+
*/
80+
@Test
81+
void testEdgeCases() {
82+
assertFalse(testInstance.solovayStrassen(-1, 10), "-1 should not be prime");
83+
assertFalse(testInstance.solovayStrassen(0, 10), "0 should not be prime");
84+
assertFalse(testInstance.solovayStrassen(1, 10), "1 should not be prime");
85+
86+
// Test small primes and composites
87+
assertTrue(testInstance.solovayStrassen(2, 1), "2 is a prime number (single iteration)");
88+
assertFalse(testInstance.solovayStrassen(9, 1), "9 is a composite number (single iteration)");
89+
90+
// Test larger primes and composites
91+
long largePrime = 104729; // Known large prime number
92+
long largeComposite = 104730; // Known composite number (even)
93+
94+
assertTrue(testInstance.solovayStrassen(largePrime, 20), "104729 is a prime number");
95+
assertFalse(testInstance.solovayStrassen(largeComposite, 20), "104730 is a composite number");
96+
97+
// Test very large numbers (may take longer)
98+
long veryLargePrime = 512927357; // Near the upper limit of long
99+
long veryLargeComposite = 512927358; // Composite (even)
100+
101+
assertTrue(testInstance.solovayStrassen(veryLargePrime, 20),
102+
Long.MAX_VALUE - 1 + " is likely a prime number.");
103+
104+
assertFalse(testInstance.solovayStrassen(veryLargeComposite, 20),
105+
Long.MAX_VALUE + " is a composite number.");
106+
}
107+
108+
/**
109+
* Tests the Jacobi symbol calculation directly for known values.
110+
* This verifies that the Jacobi symbol method behaves as expected.
111+
*/
112+
@Test
113+
void testJacobiSymbolCalculation() {
114+
// Jacobi symbol (a/n) where n is odd and positive
115+
int jacobi1 = testInstance.calculateJacobi(6, 11); // Should return -1
116+
int jacobi2 = testInstance.calculateJacobi(5, 11); // Should return +1
117+
int jacobi3 = testInstance.calculateJacobi(4, 11); // Should return +1
118+
119+
assertEquals(-1, jacobi1);
120+
assertEquals(+1, jacobi2);
121+
assertEquals(+1, jacobi3);
122+
123+
// Edge case: Jacobi symbol with even n or non-positive n
124+
int jacobi4 = testInstance.calculateJacobi(5, -11); // Should return 0 (invalid)
125+
int jacobi5 = testInstance.calculateJacobi(5, 0); // Should return 0 (invalid)
126+
127+
assertEquals(0, jacobi4);
128+
assertEquals(0, jacobi5);
129+
}
130+
}

0 commit comments

Comments
 (0)