-
Notifications
You must be signed in to change notification settings - Fork 20.4k
feat: add euler primality test #6680
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
+201
−0
Merged
Changes from 8 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
ae7e3b2
feat: add euler primality test
saahilmahato 2c1a957
refactor: use fixed seed
saahilmahato 58fc874
fix: more unit test coverage
saahilmahato 3b665ed
fix: add mock tests for edge cases
saahilmahato f846450
fix: styling issues
saahilmahato 4b099b3
refactor: remove duplicate tests
saahilmahato a787945
refactor: reduce static imports
saahilmahato 0a54ce7
refactor: remove unnecessary tests
saahilmahato e746cca
refactor: move to maths package
saahilmahato 4c57693
Merge branch 'master' into eulerpseudoprime
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
108 changes: 108 additions & 0 deletions
108
src/main/java/com/thealgorithms/maths/Prime/EulerPseudoprime.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,108 @@ | ||
package com.thealgorithms.maths.Prime; | ||
|
||
import java.math.BigInteger; | ||
import java.util.Random; | ||
|
||
/** | ||
* The {@code EulerPseudoprime} class implements the Euler primality test. | ||
* | ||
* It is based on Euler’s criterion: | ||
* For an odd prime number {@code n} and any integer {@code a} coprime to {@code n}: | ||
* a^((n-1)/2) ≡ (a/n) (mod n) | ||
* where (a/n) is the Jacobi symbol. | ||
* | ||
* This algorithm is a stronger probabilistic test than Fermat’s test. | ||
* It may still incorrectly identify a composite as “probably prime” (Euler pseudoprime), | ||
* but such cases are rare. | ||
*/ | ||
public final class EulerPseudoprime { | ||
|
||
private EulerPseudoprime() { | ||
// Private constructor to prevent instantiation. | ||
} | ||
|
||
private static final Random RANDOM = new Random(1); | ||
|
||
/** | ||
* Performs the Euler primality test for a given number. | ||
* | ||
* @param n number to test (must be > 2 and odd) | ||
* @param trials number of random bases to test | ||
* @return {@code true} if {@code n} passes all Euler tests (probably prime), | ||
* {@code false} if composite. | ||
*/ | ||
public static boolean isProbablePrime(BigInteger n, int trials) { | ||
if (n.compareTo(BigInteger.TWO) < 0) { | ||
return false; | ||
} | ||
if (n.equals(BigInteger.TWO) || n.equals(BigInteger.valueOf(3))) { | ||
return true; | ||
} | ||
if (n.mod(BigInteger.TWO).equals(BigInteger.ZERO)) { | ||
return false; | ||
} | ||
|
||
for (int i = 0; i < trials; i++) { | ||
BigInteger a = uniformRandom(BigInteger.TWO, n.subtract(BigInteger.TWO)); | ||
BigInteger jacobi = BigInteger.valueOf(jacobiSymbol(a, n)); | ||
if (jacobi.equals(BigInteger.ZERO)) { | ||
return false; | ||
} | ||
|
||
BigInteger exp = n.subtract(BigInteger.ONE).divide(BigInteger.TWO); | ||
BigInteger modExp = a.modPow(exp, n); | ||
|
||
// Euler's criterion: a^((n-1)/2) ≡ (a/n) (mod n) | ||
if (!modExp.equals(jacobi.mod(n))) { | ||
return false; // definitely composite | ||
} | ||
} | ||
return true; // probably prime | ||
} | ||
|
||
/** | ||
* Computes the Jacobi symbol (a/n). | ||
* Assumes n is positive and odd. | ||
*/ | ||
public static int jacobiSymbol(BigInteger a, BigInteger n) { | ||
if (n.signum() <= 0 || n.mod(BigInteger.TWO).equals(BigInteger.ZERO)) { | ||
throw new IllegalArgumentException("n must be positive and odd."); | ||
} | ||
|
||
int result = 1; | ||
a = a.mod(n); | ||
|
||
while (a.compareTo(BigInteger.ZERO) != 0) { | ||
while (a.mod(BigInteger.TWO).equals(BigInteger.ZERO)) { | ||
a = a.divide(BigInteger.TWO); | ||
BigInteger nMod8 = n.mod(BigInteger.valueOf(8)); | ||
if (nMod8.equals(BigInteger.valueOf(3)) || nMod8.equals(BigInteger.valueOf(5))) { | ||
result = -result; | ||
} | ||
} | ||
|
||
BigInteger temp = a; | ||
a = n; | ||
n = temp; | ||
|
||
if (a.mod(BigInteger.valueOf(4)).equals(BigInteger.valueOf(3)) && n.mod(BigInteger.valueOf(4)).equals(BigInteger.valueOf(3))) { | ||
result = -result; | ||
} | ||
|
||
a = a.mod(n); | ||
} | ||
|
||
return n.equals(BigInteger.ONE) ? result : 0; | ||
} | ||
|
||
/** | ||
* Generates a random BigInteger between {@code min} and {@code max}, inclusive. | ||
*/ | ||
private static BigInteger uniformRandom(BigInteger min, BigInteger max) { | ||
BigInteger result; | ||
do { | ||
result = new BigInteger(max.bitLength(), RANDOM); | ||
} while (result.compareTo(min) < 0 || result.compareTo(max) > 0); | ||
return result; | ||
} | ||
} |
94 changes: 94 additions & 0 deletions
94
src/test/java/com/thealgorithms/maths/prime/EulerPseudoprimeTest.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,94 @@ | ||
package com.thealgorithms.maths.prime; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertFalse; | ||
import static org.junit.jupiter.api.Assertions.assertThrows; | ||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
import static org.mockito.ArgumentMatchers.any; | ||
|
||
import com.thealgorithms.maths.Prime.EulerPseudoprime; | ||
import java.math.BigInteger; | ||
import org.junit.jupiter.api.Test; | ||
import org.mockito.MockedStatic; | ||
import org.mockito.Mockito; | ||
|
||
class EulerPseudoprimeTest { | ||
|
||
@Test | ||
void testPrimeNumbers() { | ||
assertTrue(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(7), 5)); | ||
assertTrue(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(13), 5)); | ||
assertTrue(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(101), 5)); | ||
} | ||
|
||
@Test | ||
void testCompositeNumbers() { | ||
assertFalse(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(9), 5)); | ||
assertFalse(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(21), 5)); | ||
assertFalse(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(221), 5)); | ||
} | ||
|
||
@Test | ||
void testEvenNumbers() { | ||
assertFalse(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(4), 5)); | ||
assertFalse(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(100), 5)); | ||
} | ||
|
||
@Test | ||
void testEdgeCases() { | ||
assertFalse(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(0), 5)); | ||
assertFalse(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(1), 5)); | ||
assertTrue(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(2), 5)); | ||
assertTrue(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(3), 5)); | ||
} | ||
|
||
@Test | ||
void testIsProbablePrimeWhenJacobiSymbolIsZero() { | ||
try (MockedStatic<EulerPseudoprime> mockedPrimality = Mockito.mockStatic(EulerPseudoprime.class, Mockito.CALLS_REAL_METHODS)) { | ||
|
||
// Mock jacobiSymbol to return 0 to test the branch | ||
mockedPrimality.when(() -> EulerPseudoprime.jacobiSymbol(any(BigInteger.class), any(BigInteger.class))).thenReturn(0); | ||
|
||
boolean result = EulerPseudoprime.isProbablePrime(BigInteger.valueOf(15), 1); | ||
|
||
assertFalse(result); | ||
} | ||
} | ||
|
||
@Test | ||
void testJacobiSymbolThrowsForEvenOrNonPositiveN() throws Exception { | ||
var method = EulerPseudoprime.class.getDeclaredMethod("jacobiSymbol", BigInteger.class, BigInteger.class); | ||
|
||
// Helper lambda to unwrap InvocationTargetException | ||
Runnable invokeJacobi = () -> { | ||
try { | ||
method.invoke(null, BigInteger.valueOf(2), BigInteger.valueOf(8)); | ||
} catch (Exception e) { | ||
// unwrap | ||
Throwable cause = e.getCause(); | ||
if (cause instanceof IllegalArgumentException) { | ||
throw (IllegalArgumentException) cause; | ||
} else { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
}; | ||
|
||
// Now check that it actually throws | ||
assertThrows(IllegalArgumentException.class, invokeJacobi::run); | ||
|
||
// Another case: non-positive n | ||
Runnable invokeJacobi2 = () -> { | ||
try { | ||
method.invoke(null, BigInteger.valueOf(5), BigInteger.valueOf(-3)); | ||
} catch (Exception e) { | ||
Throwable cause = e.getCause(); | ||
if (cause instanceof IllegalArgumentException) { | ||
throw (IllegalArgumentException) cause; | ||
} else { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
}; | ||
assertThrows(IllegalArgumentException.class, invokeJacobi2::run); | ||
} | ||
} |
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.