-
Notifications
You must be signed in to change notification settings - Fork 20.5k
feat: add solovay strassen primality test #5692
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+255
−0
Merged
Changes from 5 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
8fc2a55
feat: add solovay strassen primality test
saahil-mahato db29206
chore: add wikipedia link
saahil-mahato 65bbfb9
fix: format and coverage
saahil-mahato 90c14f9
fix: mvn stylecheck
saahil-mahato cbe6da9
fix: PMD errors
saahil-mahato ee248b8
refactor: make random final
saahil-mahato 0b8809a
Merge branch 'master' into solovay-strassen
alxkm 0f31aa7
Merge branch 'master' into solovay-strassen
alxkm File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
133 changes: 133 additions & 0 deletions
133
src/main/java/com/thealgorithms/maths/SolovayStrassenPrimalityTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 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. | ||
} | ||
} |
122 changes: 122 additions & 0 deletions
122
src/test/java/com/thealgorithms/maths/SolovayStrassenPrimalityTestTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
package com.thealgorithms.maths; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.junit.jupiter.api.Assertions.assertFalse; | ||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.MethodSource; | ||
|
||
/** | ||
* Unit tests for the {@link SolovayStrassenPrimalityTest} class. | ||
* This class tests the functionality of the Solovay-Strassen primality test implementation. | ||
*/ | ||
class SolovayStrassenPrimalityTestTest { | ||
|
||
private static final int RANDOM_SEED = 123; // Seed for reproducibility | ||
private SolovayStrassenPrimalityTest testInstance; | ||
|
||
/** | ||
* Sets up a new instance of {@link SolovayStrassenPrimalityTest} | ||
* before each test case, using a fixed random seed for consistency. | ||
*/ | ||
@BeforeEach | ||
void setUp() { | ||
testInstance = SolovayStrassenPrimalityTest.getSolovayStrassenPrimalityTest(RANDOM_SEED); | ||
} | ||
|
||
/** | ||
* Provides test cases for prime numbers with various values of n and k (iterations). | ||
* | ||
* @return an array of objects containing pairs of n and k values | ||
*/ | ||
static Object[][] primeNumbers() { | ||
return new Object[][] {{2, 1}, {3, 1}, {5, 5}, {7, 10}, {11, 20}, {13, 10}, {17, 5}, {19, 1}}; | ||
} | ||
|
||
/** | ||
* Tests known prime numbers with various values of n and k (iterations). | ||
* | ||
* @param n the number to be tested for primality | ||
* @param k the number of iterations to use in the primality test | ||
*/ | ||
@ParameterizedTest | ||
@MethodSource("primeNumbers") | ||
void testPrimeNumbersWithDifferentNAndK(int n, int k) { | ||
assertTrue(testInstance.solovayStrassen(n, k), n + " should be prime"); | ||
} | ||
|
||
/** | ||
* Provides test cases for composite numbers with various values of n and k (iterations). | ||
* | ||
* @return an array of objects containing pairs of n and k values | ||
*/ | ||
static Object[][] compositeNumbers() { | ||
return new Object[][] {{4, 1}, {6, 5}, {8, 10}, {9, 20}, {10, 1}, {12, 5}, {15, 10}}; | ||
} | ||
|
||
/** | ||
* Tests known composite numbers with various values of n and k (iterations). | ||
* | ||
* @param n the number to be tested for primality | ||
* @param k the number of iterations to use in the primality test | ||
*/ | ||
@ParameterizedTest | ||
@MethodSource("compositeNumbers") | ||
void testCompositeNumbersWithDifferentNAndK(int n, int k) { | ||
assertFalse(testInstance.solovayStrassen(n, k), n + " should be composite"); | ||
} | ||
|
||
/** | ||
* Tests edge cases for the primality test. | ||
* This includes negative numbers and small integers (0 and 1). | ||
*/ | ||
@Test | ||
void testEdgeCases() { | ||
assertFalse(testInstance.solovayStrassen(-1, 10), "-1 should not be prime"); | ||
assertFalse(testInstance.solovayStrassen(0, 10), "0 should not be prime"); | ||
assertFalse(testInstance.solovayStrassen(1, 10), "1 should not be prime"); | ||
|
||
// Test small primes and composites | ||
assertTrue(testInstance.solovayStrassen(2, 1), "2 is a prime number (single iteration)"); | ||
assertFalse(testInstance.solovayStrassen(9, 1), "9 is a composite number (single iteration)"); | ||
|
||
// Test larger primes and composites | ||
long largePrime = 104729; // Known large prime number | ||
long largeComposite = 104730; // Composite number (even) | ||
|
||
assertTrue(testInstance.solovayStrassen(largePrime, 20), "104729 is a prime number"); | ||
assertFalse(testInstance.solovayStrassen(largeComposite, 20), "104730 is a composite number"); | ||
|
||
// Test very large numbers (may take longer) | ||
long veryLargePrime = 512927357; // Known very large prime number | ||
long veryLargeComposite = 512927358; // Composite number (even) | ||
|
||
assertTrue(testInstance.solovayStrassen(veryLargePrime, 20), Long.MAX_VALUE - 1 + " is likely a prime number."); | ||
|
||
assertFalse(testInstance.solovayStrassen(veryLargeComposite, 20), Long.MAX_VALUE + " is a composite number."); | ||
} | ||
|
||
/** | ||
* Tests the Jacobi symbol calculation directly for known values. | ||
* This verifies that the Jacobi symbol method behaves as expected. | ||
*/ | ||
@Test | ||
void testJacobiSymbolCalculation() { | ||
// Jacobi symbol (a/n) where n is odd and positive | ||
int jacobi1 = testInstance.calculateJacobi(6, 11); // Should return -1 | ||
int jacobi2 = testInstance.calculateJacobi(5, 11); // Should return +1 | ||
|
||
assertEquals(-1, jacobi1); | ||
assertEquals(+1, jacobi2); | ||
|
||
// Edge case: Jacobi symbol with even n or non-positive n | ||
int jacobi4 = testInstance.calculateJacobi(5, -11); // Should return 0 (invalid) | ||
int jacobi5 = testInstance.calculateJacobi(5, 0); // Should return 0 (invalid) | ||
|
||
assertEquals(0, jacobi4); | ||
assertEquals(0, jacobi5); | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.