Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions src/main/java/com/thealgorithms/maths/EulerPseudoprime.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package com.thealgorithms.maths;

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;
}
}
93 changes: 93 additions & 0 deletions src/test/java/com/thealgorithms/maths/EulerPseudoprimeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.thealgorithms.maths;

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 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);
}
}