Files
Java/src/main/java/com/thealgorithms/maths/EulerPseudoprime.java
Saahil Mahato 9f985b2549 feat: add euler primality test (#6680)
* feat: add euler primality test

* refactor: use fixed seed

* fix: more unit test coverage

* fix: add mock tests for edge cases

* fix: styling issues

* refactor: remove duplicate tests

* refactor: reduce static imports

* refactor: remove unnecessary tests

* refactor: move to maths package

---------

Co-authored-by: a <alexanderklmn@gmail.com>
2025-10-06 20:23:32 +02:00

109 lines
3.5 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 Eulers 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 Fermats 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;
}
}