diff --git a/README.md b/README.md index 22a934dd3..30ff21991 100644 --- a/README.md +++ b/README.md @@ -242,15 +242,14 @@ $ java -cp classes com.williamfiset.algorithms.search.BinarySearch - [[UNTESTED] Chinese remainder theorem](src/main/java/com/williamfiset/algorithms/math/ChineseRemainderTheorem.java) - [Prime number sieve (sieve of Eratosthenes)](src/main/java/com/williamfiset/algorithms/math/SieveOfEratosthenes.java) **- O(nlog(log(n)))** - [Prime number sieve (sieve of Eratosthenes, compressed)](src/main/java/com/williamfiset/algorithms/math/CompressedPrimeSieve.java) **- O(nlog(log(n)))** -- [Totient function (phi function, relatively prime number count)](src/main/java/com/williamfiset/algorithms/math/EulerTotientFunction.java) **- O(n1/4)** -- [Totient function using sieve (phi function, relatively prime number count)](src/main/java/com/williamfiset/algorithms/math/EulerTotientFunctionWithSieve.java) **- O(nlog(log(n)))** +- [Totient function (phi function, relatively prime number count)](src/main/java/com/williamfiset/algorithms/math/EulerTotientFunction.java) **- O(√n)** - [Extended euclidean algorithm](src/main/java/com/williamfiset/algorithms/math/ExtendedEuclideanAlgorithm.java) **- ~O(log(a + b))** -- [Greatest Common Divisor (GCD)](src/main/java/com/williamfiset/algorithms/math/GCD.java) **- ~O(log(a + b))** +- [Greatest Common Divisor (GCD)](src/main/java/com/williamfiset/algorithms/math/Gcd.java) **- ~O(log(a + b))** - [Fast Fourier transform (quick polynomial multiplication)](src/main/java/com/williamfiset/algorithms/math/FastFourierTransform.java) **- O(nlog(n))** - [Fast Fourier transform (quick polynomial multiplication, complex numbers)](src/main/java/com/williamfiset/algorithms/math/FastFourierTransformComplexNumbers.java) **- O(nlog(n))** -- [Primality check](src/main/java/com/williamfiset/algorithms/math/IsPrime.java) **- O(√n)** +- [Primality check](src/main/java/com/williamfiset/algorithms/math/PrimalityCheck.java) **- O(√n)** - [Primality check (Rabin-Miller)](src/main/java/com/williamfiset/algorithms/math/RabinMillerPrimalityTest.py) **- O(k)** -- [Least Common Multiple (LCM)](src/main/java/com/williamfiset/algorithms/math/LCM.java) **- ~O(log(a + b))** +- [Least Common Multiple (LCM)](src/main/java/com/williamfiset/algorithms/math/Lcm.java) **- ~O(log(a + b))** - [Modular inverse](src/main/java/com/williamfiset/algorithms/math/ModularInverse.java) **- ~O(log(a + b))** - [Prime factorization (pollard rho)](src/main/java/com/williamfiset/algorithms/math/PrimeFactorization.java) **- O(n1/4)** - [Relatively prime check (coprimality check)](src/main/java/com/williamfiset/algorithms/math/RelativelyPrime.java) **- ~O(log(a + b))** diff --git a/src/main/java/com/williamfiset/algorithms/math/BUILD b/src/main/java/com/williamfiset/algorithms/math/BUILD index a47a91f5e..d99722864 100644 --- a/src/main/java/com/williamfiset/algorithms/math/BUILD +++ b/src/main/java/com/williamfiset/algorithms/math/BUILD @@ -21,13 +21,6 @@ java_binary( runtime_deps = [":math"], ) -# bazel run //src/main/java/com/williamfiset/algorithms/math:EulerTotientFunctionWithSieve -java_binary( - name = "EulerTotientFunctionWithSieve", - main_class = "com.williamfiset.algorithms.math.EulerTotientFunctionWithSieve", - runtime_deps = [":math"], -) - # bazel run //src/main/java/com/williamfiset/algorithms/math:FastFourierTransform java_binary( name = "FastFourierTransform", @@ -35,24 +28,24 @@ java_binary( runtime_deps = [":math"], ) -# bazel run //src/main/java/com/williamfiset/algorithms/math:GCD +# bazel run //src/main/java/com/williamfiset/algorithms/math:Gcd java_binary( - name = "GCD", - main_class = "com.williamfiset.algorithms.math.GCD", + name = "Gcd", + main_class = "com.williamfiset.algorithms.math.Gcd", runtime_deps = [":math"], ) -# bazel run //src/main/java/com/williamfiset/algorithms/math:IsPrime +# bazel run //src/main/java/com/williamfiset/algorithms/math:PrimalityCheck java_binary( - name = "IsPrime", - main_class = "com.williamfiset.algorithms.math.IsPrime", + name = "PrimalityCheck", + main_class = "com.williamfiset.algorithms.math.PrimalityCheck", runtime_deps = [":math"], ) -# bazel run //src/main/java/com/williamfiset/algorithms/math:LCM +# bazel run //src/main/java/com/williamfiset/algorithms/math:Lcm java_binary( - name = "LCM", - main_class = "com.williamfiset.algorithms.math.LCM", + name = "Lcm", + main_class = "com.williamfiset.algorithms.math.Lcm", runtime_deps = [":math"], ) diff --git a/src/main/java/com/williamfiset/algorithms/math/CompressedPrimeSieve.java b/src/main/java/com/williamfiset/algorithms/math/CompressedPrimeSieve.java index ef542cf5b..5b37eebbc 100644 --- a/src/main/java/com/williamfiset/algorithms/math/CompressedPrimeSieve.java +++ b/src/main/java/com/williamfiset/algorithms/math/CompressedPrimeSieve.java @@ -1,76 +1,70 @@ /** - * Generate a compressed prime sieve using bit manipulation. The idea is that each bit represents a - * boolean value indicating whether a number is prime or not. This saves a lot of room when creating - * the sieve. In this implementation I store all odd numbers in individual longs meaning that for - * each long I use I can represent a range of 128 numbers (even numbers are omitted because they are - * not prime, with the exception of 2 which is handled as a special case). + * Generates a compressed prime sieve using bit manipulation. Each bit represents whether an odd + * number is prime or not. Even numbers are omitted (except 2, handled as a special case), so each + * long covers a range of 128 numbers. * - *
Time Complexity: ~O(nloglogn) + *
Time: ~O(n log(log(n))) * - *
Compile: javac -d src/main/java - * src/main/java/com/williamfiset/algorithms/math/CompressedPrimeSieve.java - * - *
Run: java -cp src/main/java com/williamfiset/algorithms/math/CompressedPrimeSieve + *
Space: O(n / 128) longs * * @author William Fiset, william.alexandre.fiset@gmail.com */ package com.williamfiset.algorithms.math; public class CompressedPrimeSieve { + private static final double NUM_BITS = 128.0; private static final int NUM_BITS_SHIFT = 7; // 2^7 = 128 - // Sets the bit representing n to 1 indicating this number is not prime + // Marks n as not prime by setting its bit to 1. private static void setBit(long[] arr, int n) { - if ((n & 1) == 0) return; // n is even + if ((n & 1) == 0) + return; arr[n >> NUM_BITS_SHIFT] |= 1L << ((n - 1) >> 1); } - // Returns true if the bit for n is off (meaning n is a prime). - // Note: do use this method to access numbers outside your prime sieve range! + // Returns true if n's bit is unset (meaning n is prime). private static boolean isNotSet(long[] arr, int n) { - if (n < 2) return false; // n is not prime - if (n == 2) return true; // two is prime - if ((n & 1) == 0) return false; // n is even + if (n < 2) + return false; + if (n == 2) + return true; + if ((n & 1) == 0) + return false; long chunk = arr[n >> NUM_BITS_SHIFT]; long mask = 1L << ((n - 1) >> 1); return (chunk & mask) != mask; } - // Returns true/false depending on whether n is prime. + /** Returns true if n is prime according to the given sieve. */ public static boolean isPrime(long[] sieve, int n) { return isNotSet(sieve, n); } - // Returns an array of longs with each bit indicating whether a number - // is prime or not. Use the isNotSet and setBit methods to toggle to bits for each number. + /** + * Builds a compressed prime sieve for all numbers up to {@code limit}. + * + * @param limit the upper bound (inclusive) for the sieve. + * @return a bit-packed array where each bit indicates whether an odd number is composite. + */ public static long[] primeSieve(int limit) { - final int numChunks = (int) Math.ceil(limit / NUM_BITS); - final int sqrtLimit = (int) Math.sqrt(limit); - // if (limit < 2) return 0; // uncomment for primeCount purposes - // int primeCount = (int) Math.ceil(limit / 2.0); // Counts number of primes <= limit + int numChunks = (int) Math.ceil(limit / NUM_BITS); + int sqrtLimit = (int) Math.sqrt(limit); long[] chunks = new long[numChunks]; - chunks[0] = 1; // 1 as not prime + chunks[0] = 1; // Mark 1 as not prime. for (int i = 3; i <= sqrtLimit; i += 2) if (isNotSet(chunks, i)) for (int j = i * i; j <= limit; j += i) - if (isNotSet(chunks, j)) { + if (isNotSet(chunks, j)) setBit(chunks, j); - // primeCount--; - } return chunks; } - /* Example usage. */ - public static void main(String[] args) { - final int limit = 200; - long[] sieve = CompressedPrimeSieve.primeSieve(limit); - - for (int i = 0; i <= limit; i++) { - if (CompressedPrimeSieve.isPrime(sieve, i)) { + int limit = 200; + long[] sieve = primeSieve(limit); + for (int i = 0; i <= limit; i++) + if (isPrime(sieve, i)) System.out.printf("%d is prime!\n", i); - } - } } } diff --git a/src/main/java/com/williamfiset/algorithms/math/EulerTotientFunction.java b/src/main/java/com/williamfiset/algorithms/math/EulerTotientFunction.java index d5a00f232..7ffbfe7d8 100644 --- a/src/main/java/com/williamfiset/algorithms/math/EulerTotientFunction.java +++ b/src/main/java/com/williamfiset/algorithms/math/EulerTotientFunction.java @@ -1,81 +1,49 @@ +/** + * Computes Euler's totient function phi(n), which counts the number of integers in [1, n] that are + * relatively prime to n. + * + *
Uses trial division to find prime factors and applies the product formula: + * phi(n) = n * product of (1 - 1/p) for each distinct prime factor p of n. + * + *
Time: O(sqrt(n))
+ *
+ * @author William Fiset, william.alexandre.fiset@gmail.com
+ */
package com.williamfiset.algorithms.math;
-import java.util.*;
-
public class EulerTotientFunction {
+ /**
+ * Computes Euler's totient phi(n).
+ *
+ * @param n a positive integer.
+ * @return the number of integers in [1, n] that are coprime to n.
+ * @throws IllegalArgumentException if n is not positive.
+ */
public static long eulersTotient(long n) {
- for (long p : new HashSet Time: ~O(log(a + b))
+ *
+ * @author William Fiset, william.alexandre.fiset@gmail.com
+ */
package com.williamfiset.algorithms.math;
public class ExtendedEuclideanAlgorithm {
- // This function performs the extended euclidean algorithm on two numbers a and b.
- // The function returns the gcd(a,b) as well as the numbers x and y such
- // that ax + by = gcd(a,b). This calculation is important in number theory
- // and can be used for several things such as finding modular inverses and
- // solutions to linear Diophantine equations.
+ /**
+ * Performs the extended Euclidean algorithm on a and b.
+ *
+ * @return an array [gcd(a, b), x, y] such that ax + by = gcd(a, b).
+ */
public static long[] egcd(long a, long b) {
- if (b == 0) return new long[] {a, 1, 0};
- else {
- long[] ret = egcd(b, a % b);
- long tmp = ret[1] - ret[2] * (a / b);
- ret[1] = ret[2];
- ret[2] = tmp;
- return ret;
- }
+ if (b == 0)
+ return new long[] {a, 1, 0};
+ long[] ret = egcd(b, a % b);
+ long tmp = ret[1] - ret[2] * (a / b);
+ ret[1] = ret[2];
+ ret[2] = tmp;
+ return ret;
+ }
+
+ public static void main(String[] args) {
+ // egcd(35, 15) = [5, 1, -2] because gcd(35,15)=5 and 35*1 + 15*(-2) = 5.
+ long[] result = egcd(35, 15);
+ System.out.printf("gcd(%d, %d) = %d, x = %d, y = %d\n", 35, 15, result[0], result[1], result[2]);
+
+ // Finding modular inverse: 7^(-1) mod 11 = 8 because 7*8 mod 11 = 1.
+ long[] inv = egcd(7, 11);
+ long modInverse = ((inv[1] % 11) + 11) % 11;
+ System.out.printf("7^(-1) mod 11 = %d\n", modInverse);
}
}
diff --git a/src/main/java/com/williamfiset/algorithms/math/GCD.java b/src/main/java/com/williamfiset/algorithms/math/GCD.java
deleted file mode 100644
index c479842c7..000000000
--- a/src/main/java/com/williamfiset/algorithms/math/GCD.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * An implementation of finding the GCD of two numbers
- *
- * Time Complexity ~O(log(a + b))
- *
- * @author William Fiset, william.alexandre.fiset@gmail.com
- */
-package com.williamfiset.algorithms.math;
-
-public class GCD {
-
- // Computes the Greatest Common Divisor (GCD) of a & b
- // This method ensures that the value returned is non negative
- public static long gcd(long a, long b) {
- return b == 0 ? (a < 0 ? -a : a) : gcd(b, a % b);
- }
-
- public static void main(String[] args) {
- System.out.println(gcd(12, 18)); // 6
- System.out.println(gcd(-12, 18)); // 6
- System.out.println(gcd(12, -18)); // 6
- System.out.println(gcd(-12, -18)); // 6
-
- System.out.println(gcd(5, 0)); // 5
- System.out.println(gcd(0, 5)); // 5
- System.out.println(gcd(-5, 0)); // 5
- System.out.println(gcd(0, -5)); // 5
- System.out.println(gcd(0, 0)); // 0
- }
-}
diff --git a/src/main/java/com/williamfiset/algorithms/math/Gcd.java b/src/main/java/com/williamfiset/algorithms/math/Gcd.java
new file mode 100644
index 000000000..cf7d53ce8
--- /dev/null
+++ b/src/main/java/com/williamfiset/algorithms/math/Gcd.java
@@ -0,0 +1,34 @@
+/**
+ * Computes the Greatest Common Divisor (GCD) of two numbers using the Euclidean algorithm.
+ *
+ * Time: ~O(log(a + b))
+ *
+ * @author William Fiset, william.alexandre.fiset@gmail.com
+ */
+package com.williamfiset.algorithms.math;
+
+public class Gcd {
+
+ /**
+ * Computes the Greatest Common Divisor (GCD) of a and b. The returned value is always
+ * non-negative.
+ */
+ public static long gcd(long a, long b) {
+ if (b == 0)
+ return Math.abs(a);
+ return gcd(b, a % b);
+ }
+
+ public static void main(String[] args) {
+ System.out.println(gcd(12, 18)); // 6
+ System.out.println(gcd(-12, 18)); // 6
+ System.out.println(gcd(12, -18)); // 6
+ System.out.println(gcd(-12, -18)); // 6
+
+ System.out.println(gcd(5, 0)); // 5
+ System.out.println(gcd(0, 5)); // 5
+ System.out.println(gcd(-5, 0)); // 5
+ System.out.println(gcd(0, -5)); // 5
+ System.out.println(gcd(0, 0)); // 0
+ }
+}
diff --git a/src/main/java/com/williamfiset/algorithms/math/IsPrime.java b/src/main/java/com/williamfiset/algorithms/math/IsPrime.java
deleted file mode 100644
index c758b20f3..000000000
--- a/src/main/java/com/williamfiset/algorithms/math/IsPrime.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * Tests whether a number is a prime number or not Time Complexity: O(sqrt(n))
- *
- * @author Micah Stairs, William Fiset
- */
-package com.williamfiset.algorithms.math;
-
-public class IsPrime {
-
- public static boolean isPrime(final long n) {
- if (n < 2) return false;
- if (n == 2 || n == 3) return true;
- if (n % 2 == 0 || n % 3 == 0) return false;
-
- long limit = (long) Math.sqrt(n);
-
- for (long i = 5; i <= limit; i += 6) {
- if (n % i == 0 || n % (i + 2) == 0) {
- return false;
- }
- }
- return true;
- }
-
- public static void main(String[] args) {
- System.out.println(isPrime(5));
- System.out.println(isPrime(31));
- System.out.println(isPrime(1433));
- System.out.println(isPrime(8763857775536878331L));
- }
-}
diff --git a/src/main/java/com/williamfiset/algorithms/math/LCM.java b/src/main/java/com/williamfiset/algorithms/math/LCM.java
deleted file mode 100644
index d68b09001..000000000
--- a/src/main/java/com/williamfiset/algorithms/math/LCM.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * An implementation of finding the LCM of two numbers
- *
- * Time Complexity ~O(log(a + b))
- *
- * @author William Fiset, william.alexandre.fiset@gmail.com
- */
-package com.williamfiset.algorithms.math;
-
-public class LCM {
-
- // Finds the greatest common divisor of a and b
- private static long gcd(long a, long b) {
- return b == 0 ? (a < 0 ? -a : a) : gcd(b, a % b);
- }
-
- // Finds the least common multiple of a and b
- public static long lcm(long a, long b) {
- long lcm = (a / gcd(a, b)) * b;
- return lcm > 0 ? lcm : -lcm;
- }
-
- public static void main(String[] args) {
- System.out.println(lcm(12, 18)); // 36
- System.out.println(lcm(-12, 18)); // 36
- System.out.println(lcm(12, -18)); // 36
- System.out.println(lcm(-12, -18)); // 36
- }
-}
diff --git a/src/main/java/com/williamfiset/algorithms/math/Lcm.java b/src/main/java/com/williamfiset/algorithms/math/Lcm.java
new file mode 100644
index 000000000..9024f36df
--- /dev/null
+++ b/src/main/java/com/williamfiset/algorithms/math/Lcm.java
@@ -0,0 +1,30 @@
+/**
+ * Computes the Least Common Multiple (LCM) of two numbers using the relation LCM(a, b) = |a /
+ * gcd(a, b) * b|.
+ *
+ * Time: ~O(log(a + b))
+ *
+ * @author William Fiset, william.alexandre.fiset@gmail.com
+ */
+package com.williamfiset.algorithms.math;
+
+public class Lcm {
+
+ /** Returns the least common multiple of a and b. The result is always non-negative. */
+ public static long lcm(long a, long b) {
+ return Math.abs(a / gcd(a, b) * b);
+ }
+
+ private static long gcd(long a, long b) {
+ if (b == 0)
+ return Math.abs(a);
+ return gcd(b, a % b);
+ }
+
+ public static void main(String[] args) {
+ System.out.println(lcm(12, 18)); // 36
+ System.out.println(lcm(-12, 18)); // 36
+ System.out.println(lcm(12, -18)); // 36
+ System.out.println(lcm(-12, -18)); // 36
+ }
+}
diff --git a/src/main/java/com/williamfiset/algorithms/math/ModularInverse.java b/src/main/java/com/williamfiset/algorithms/math/ModularInverse.java
index e1cab90cb..6929e0b02 100644
--- a/src/main/java/com/williamfiset/algorithms/math/ModularInverse.java
+++ b/src/main/java/com/williamfiset/algorithms/math/ModularInverse.java
@@ -1,15 +1,39 @@
-/** Time Complexity ~O(log(a + b)) */
+/**
+ * Computes the modular inverse of a number using the Extended Euclidean Algorithm.
+ *
+ * The modular inverse of 'a' mod 'm' is a value x such that a*x ≡ 1 (mod m). It exists if and
+ * only if gcd(a, m) = 1 (i.e. a and m are coprime).
+ *
+ * Time: ~O(log(a + m))
+ *
+ * @author William Fiset, william.alexandre.fiset@gmail.com
+ */
package com.williamfiset.algorithms.math;
public class ModularInverse {
- // This function performs the extended euclidean algorithm on two numbers a and b.
- // The function returns the gcd(a,b) as well as the numbers x and y such
- // that ax + by = gcd(a,b). This calculation is important in number theory
- // and can be used for several things such as finding modular inverses and
- // solutions to linear Diophantine equations.
+ /**
+ * Returns the modular inverse of 'a' mod 'm', or null if it does not exist.
+ *
+ * @param a the value to invert.
+ * @param m the modulus (must be positive).
+ * @return the modular inverse, or null if gcd(a, m) != 1.
+ * @throws ArithmeticException if m is not positive.
+ */
+ public static Long modInv(long a, long m) {
+ if (m <= 0)
+ throw new ArithmeticException("mod must be > 0");
+ a = ((a % m) + m) % m;
+ long[] v = egcd(a, m);
+ if (v[0] != 1)
+ return null;
+ return ((v[1] % m) + m) % m;
+ }
+
+ // Returns [gcd(a, b), x, y] such that ax + by = gcd(a, b).
private static long[] egcd(long a, long b) {
- if (b == 0) return new long[] {a, 1L, 0L};
+ if (b == 0)
+ return new long[] {a, 1, 0};
long[] v = egcd(b, a % b);
long tmp = v[1] - v[2] * (a / b);
v[1] = v[2];
@@ -17,30 +41,11 @@ private static long[] egcd(long a, long b) {
return v;
}
- // Returns the modular inverse of 'a' mod 'm' if it exists.
- // Make sure m > 0 and 'a' & 'm' are relatively prime.
- public static Long modInv(long a, long m) {
-
- if (m <= 0) throw new ArithmeticException("mod must be > 0");
-
- // Avoid a being negative
- a = ((a % m) + m) % m;
-
- long[] v = egcd(a, m);
- long gcd = v[0];
- long x = v[1];
-
- if (gcd != 1) return null;
- return ((x + m) % m) % m;
- }
-
public static void main(String[] args) {
-
- // Prints 3 since 2*3 mod 5 = 1
+ // 2*3 mod 5 = 1, so modInv(2, 5) = 3.
System.out.println(modInv(2, 5));
- // Prints null because there is no
- // modular inverse such that 4*x mod 18 = 1
+ // gcd(4, 18) != 1, so no inverse exists.
System.out.println(modInv(4, 18));
}
}
diff --git a/src/main/java/com/williamfiset/algorithms/math/PrimalityCheck.java b/src/main/java/com/williamfiset/algorithms/math/PrimalityCheck.java
new file mode 100644
index 000000000..63b9b70a7
--- /dev/null
+++ b/src/main/java/com/williamfiset/algorithms/math/PrimalityCheck.java
@@ -0,0 +1,85 @@
+/**
+ * Tests whether a number is prime using trial division. Skips multiples of 2 and 3 by iterating
+ * over numbers of the form 6k ± 1.
+ *
+ * Why 6k ± 1? Every integer falls into one of six residue classes mod 6:
+ *
+ * Time: O(√n)
+ *
+ * @author Micah Stairs, William Fiset
+ */
+package com.williamfiset.algorithms.math;
+
+public class PrimalityCheck {
+
+ /** Returns true if n is a prime number. */
+ public static boolean isPrime(long n) {
+ if (n < 2)
+ return false;
+ if (n == 2 || n == 3)
+ return true;
+ if (n % 2 == 0 || n % 3 == 0)
+ return false;
+ long limit = (long) Math.sqrt(n);
+ for (long i = 5; i <= limit; i += 6)
+ if (n % i == 0 || n % (i + 2) == 0)
+ return false;
+ return true;
+ }
+
+ /**
+ * Optimized primality check using a mod 30 wheel. This is a balanced generalization of the mod 6
+ * approach above — by also eliminating multiples of 5, we only test 8 out of every 30 candidates
+ * (27%) instead of 2 out of every 6 (33%).
+ *
+ * The 8 residues coprime to 30 (= 2·3·5) are: 1, 7, 11, 13, 17, 19, 23, 29. Larger wheels
+ * (mod 210 = 2·3·5·7 with 48 offsets) exist but offer diminishing returns for the added code
+ * complexity.
+ */
+ public static boolean isPrimeWheel30(long n) {
+ if (n < 2)
+ return false;
+ if (n == 2 || n == 3 || n == 5)
+ return true;
+ if (n % 2 == 0 || n % 3 == 0 || n % 5 == 0)
+ return false;
+ // Gaps between consecutive residues coprime to 30: 7,11,13,17,19,23,29,31 (i.e. +4,+2,+4,+2,+4,+6,+2,+6).
+ int[] gaps = {4, 2, 4, 2, 4, 6, 2, 6};
+ long limit = (long) Math.sqrt(n);
+ int gi = 0;
+ for (long d = 7; d <= limit; d += gaps[gi++ % 8])
+ if (n % d == 0)
+ return false;
+ return true;
+ }
+
+ public static void main(String[] args) {
+ System.out.println(isPrime(5)); // true
+ System.out.println(isPrime(31)); // true
+ System.out.println(isPrime(1433)); // true
+ System.out.println(isPrime(8763857775536878331L)); // true
+ System.out.println(isPrime(4)); // false
+ System.out.println(isPrime(15)); // false
+
+ System.out.println();
+
+ System.out.println(isPrimeWheel30(5)); // true
+ System.out.println(isPrimeWheel30(31)); // true
+ System.out.println(isPrimeWheel30(1433)); // true
+ System.out.println(isPrimeWheel30(8763857775536878331L)); // true
+ System.out.println(isPrimeWheel30(4)); // false
+ System.out.println(isPrimeWheel30(15)); // false
+ }
+}
diff --git a/src/test/java/com/williamfiset/algorithms/math/BUILD b/src/test/java/com/williamfiset/algorithms/math/BUILD
new file mode 100644
index 000000000..12257dace
--- /dev/null
+++ b/src/test/java/com/williamfiset/algorithms/math/BUILD
@@ -0,0 +1,38 @@
+load("@rules_java//java:defs.bzl", "java_test")
+
+# Common dependencies for JUnit 5 tests
+JUNIT5_DEPS = [
+ "@maven//:org_junit_jupiter_junit_jupiter_api",
+ "@maven//:org_junit_jupiter_junit_jupiter_engine",
+]
+
+JUNIT5_RUNTIME_DEPS = [
+ "@maven//:org_junit_platform_junit_platform_console",
+]
+
+TEST_DEPS = [
+ "//src/main/java/com/williamfiset/algorithms/math:math",
+ "@maven//:com_google_truth_truth",
+] + JUNIT5_DEPS
+
+# bazel test //src/test/java/com/williamfiset/algorithms/math:CompressedPrimeSieveTest
+java_test(
+ name = "CompressedPrimeSieveTest",
+ srcs = ["CompressedPrimeSieveTest.java"],
+ main_class = "org.junit.platform.console.ConsoleLauncher",
+ use_testrunner = False,
+ args = ["--select-class=com.williamfiset.algorithms.math.CompressedPrimeSieveTest"],
+ runtime_deps = JUNIT5_RUNTIME_DEPS,
+ deps = TEST_DEPS,
+)
+
+# bazel test //src/test/java/com/williamfiset/algorithms/math:EulerTotientFunctionTest
+java_test(
+ name = "EulerTotientFunctionTest",
+ srcs = ["EulerTotientFunctionTest.java"],
+ main_class = "org.junit.platform.console.ConsoleLauncher",
+ use_testrunner = False,
+ args = ["--select-class=com.williamfiset.algorithms.math.EulerTotientFunctionTest"],
+ runtime_deps = JUNIT5_RUNTIME_DEPS,
+ deps = TEST_DEPS,
+)
diff --git a/src/test/java/com/williamfiset/algorithms/math/CompressedPrimeSieveTest.java b/src/test/java/com/williamfiset/algorithms/math/CompressedPrimeSieveTest.java
new file mode 100644
index 000000000..f9cbb591f
--- /dev/null
+++ b/src/test/java/com/williamfiset/algorithms/math/CompressedPrimeSieveTest.java
@@ -0,0 +1,77 @@
+package com.williamfiset.algorithms.math;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.jupiter.api.*;
+
+public class CompressedPrimeSieveTest {
+
+ // First 25 primes (all primes <= 100).
+ private static final int[] PRIMES_UP_TO_100 = {
+ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89,
+ 97
+ };
+
+ @Test
+ public void testPrimesUpTo100() {
+ long[] sieve = CompressedPrimeSieve.primeSieve(100);
+ java.util.Set
+ * 6k + 0 → divisible by 6 (= 2·3) → not prime
+ * 6k + 1 → not divisible by 2 or 3 → candidate ✓
+ * 6k + 2 → divisible by 2 → not prime
+ * 6k + 3 → divisible by 3 → not prime
+ * 6k + 4 → divisible by 2 → not prime
+ * 6k + 5 → not divisible by 2 or 3 → candidate ✓ (same as 6k - 1)
+ *
+ *
+ * After checking 2 and 3 directly, all remaining primes must be of the form 6k ± 1, so we only
+ * need to test divisibility by those candidates.
+ *
+ *