From 785002a9fcb0defd602c471295770132b2b840b7 Mon Sep 17 00:00:00 2001 From: William Fiset Date: Wed, 25 Mar 2026 09:13:44 -0700 Subject: [PATCH 1/8] Refactor GCD: rename to Gcd, add docs, clean up Co-Authored-By: Claude Opus 4.6 --- README.md | 2 +- .../com/williamfiset/algorithms/math/BUILD | 6 ++-- .../com/williamfiset/algorithms/math/GCD.java | 30 ---------------- .../com/williamfiset/algorithms/math/Gcd.java | 34 +++++++++++++++++++ 4 files changed, 38 insertions(+), 34 deletions(-) delete mode 100644 src/main/java/com/williamfiset/algorithms/math/GCD.java create mode 100644 src/main/java/com/williamfiset/algorithms/math/Gcd.java diff --git a/README.md b/README.md index 22a934dd3..0566f010c 100644 --- a/README.md +++ b/README.md @@ -245,7 +245,7 @@ $ java -cp classes com.williamfiset.algorithms.search.BinarySearch - [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)))** - [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)** diff --git a/src/main/java/com/williamfiset/algorithms/math/BUILD b/src/main/java/com/williamfiset/algorithms/math/BUILD index a47a91f5e..7c4352bcc 100644 --- a/src/main/java/com/williamfiset/algorithms/math/BUILD +++ b/src/main/java/com/williamfiset/algorithms/math/BUILD @@ -35,10 +35,10 @@ 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"], ) 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 + } +} From 8f36c11c7502100ff8c27dc21077d4e06eb4994b Mon Sep 17 00:00:00 2001 From: William Fiset Date: Wed, 25 Mar 2026 09:23:34 -0700 Subject: [PATCH 2/8] Refactor CompressedPrimeSieve: add docs, clean up, add tests Co-Authored-By: Claude Opus 4.6 --- .../algorithms/math/CompressedPrimeSieve.java | 68 ++++++++-------- .../com/williamfiset/algorithms/math/BUILD | 27 +++++++ .../math/CompressedPrimeSieveTest.java | 77 +++++++++++++++++++ 3 files changed, 135 insertions(+), 37 deletions(-) create mode 100644 src/test/java/com/williamfiset/algorithms/math/BUILD create mode 100644 src/test/java/com/williamfiset/algorithms/math/CompressedPrimeSieveTest.java 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/test/java/com/williamfiset/algorithms/math/BUILD b/src/test/java/com/williamfiset/algorithms/math/BUILD new file mode 100644 index 000000000..5d3a10fab --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/math/BUILD @@ -0,0 +1,27 @@ +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, +) 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 expected = new java.util.HashSet<>(); + for (int p : PRIMES_UP_TO_100) + expected.add(p); + + for (int i = 0; i <= 100; i++) { + if (expected.contains(i)) + assertThat(CompressedPrimeSieve.isPrime(sieve, i)).isTrue(); + else + assertThat(CompressedPrimeSieve.isPrime(sieve, i)).isFalse(); + } + } + + @Test + public void testZeroAndOneAreNotPrime() { + long[] sieve = CompressedPrimeSieve.primeSieve(10); + assertThat(CompressedPrimeSieve.isPrime(sieve, 0)).isFalse(); + assertThat(CompressedPrimeSieve.isPrime(sieve, 1)).isFalse(); + } + + @Test + public void testTwoIsPrime() { + long[] sieve = CompressedPrimeSieve.primeSieve(10); + assertThat(CompressedPrimeSieve.isPrime(sieve, 2)).isTrue(); + } + + @Test + public void testEvenNumbersAreNotPrime() { + long[] sieve = CompressedPrimeSieve.primeSieve(200); + for (int i = 4; i <= 200; i += 2) + assertThat(CompressedPrimeSieve.isPrime(sieve, i)).isFalse(); + } + + @Test + public void testPrimeCount() { + // There are 168 primes <= 1000. + long[] sieve = CompressedPrimeSieve.primeSieve(1000); + int count = 0; + for (int i = 0; i <= 1000; i++) + if (CompressedPrimeSieve.isPrime(sieve, i)) + count++; + assertThat(count).isEqualTo(168); + } + + @Test + public void testLargePrimes() { + long[] sieve = CompressedPrimeSieve.primeSieve(10000); + // Some known primes near the upper range. + assertThat(CompressedPrimeSieve.isPrime(sieve, 9973)).isTrue(); + assertThat(CompressedPrimeSieve.isPrime(sieve, 9967)).isTrue(); + // Some known composites near the upper range. + assertThat(CompressedPrimeSieve.isPrime(sieve, 9999)).isFalse(); + assertThat(CompressedPrimeSieve.isPrime(sieve, 10000)).isFalse(); + } + + @Test + public void testSmallLimit() { + long[] sieve = CompressedPrimeSieve.primeSieve(2); + assertThat(CompressedPrimeSieve.isPrime(sieve, 2)).isTrue(); + } +} From a1306d84ae68ccd2ec2f35d767ea4d0468887395 Mon Sep 17 00:00:00 2001 From: William Fiset Date: Wed, 25 Mar 2026 09:38:59 -0700 Subject: [PATCH 3/8] Refactor EulerTotientFunction: simplify with trial division, add tests Co-Authored-By: Claude Opus 4.6 --- .../algorithms/math/EulerTotientFunction.java | 96 +++++++------------ .../com/williamfiset/algorithms/math/BUILD | 11 +++ .../math/EulerTotientFunctionTest.java | 55 +++++++++++ 3 files changed, 98 insertions(+), 64 deletions(-) create mode 100644 src/test/java/com/williamfiset/algorithms/math/EulerTotientFunctionTest.java 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(primeFactorization(n))) n -= (n / p); - return n; - } - - private static ArrayList primeFactorization(long n) { - ArrayList factors = new ArrayList(); - if (n <= 0) throw new IllegalArgumentException(); - else if (n == 1) return factors; - PriorityQueue divisorQueue = new PriorityQueue(); - divisorQueue.add(n); - while (!divisorQueue.isEmpty()) { - long divisor = divisorQueue.remove(); - if (isPrime(divisor)) { - factors.add(divisor); - continue; - } - long next_divisor = pollardRho(divisor); - if (next_divisor == divisor) { - divisorQueue.add(divisor); - } else { - divisorQueue.add(next_divisor); - divisorQueue.add(divisor / next_divisor); + if (n <= 0) + throw new IllegalArgumentException("n must be positive."); + long result = n; + for (long p = 2; p * p <= n; p++) { + if (n % p == 0) { + while (n % p == 0) + n /= p; + result -= result / p; } } - return factors; - } - - private static long pollardRho(long n) { - if (n % 2 == 0) return 2; - // Get a number in the range [2, 10^6] - long x = 2 + (long) (999999 * Math.random()); - long c = 2 + (long) (999999 * Math.random()); - long y = x; - long d = 1; - while (d == 1) { - x = (x * x + c) % n; - y = (y * y + c) % n; - y = (y * y + c) % n; - d = gcf(Math.abs(x - y), n); - if (d == n) break; - } - return d; - } - - private static long gcf(long a, long b) { - return b == 0 ? a : gcf(b, a % b); - } - - private 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; - - int limit = (int) Math.sqrt(n); - - for (int i = 5; i <= limit; i += 6) if (n % i == 0 || n % (i + 2) == 0) return false; - - return true; + // If n still has a prime factor greater than sqrt(original n). + if (n > 1) + result -= result / n; + return result; } public static void main(String[] args) { - - // Prints 8 because 1,2,4,7,8,11,13,14 are all - // less than 15 and relatively prime with 15 + // phi(15) = 8 because 1,2,4,7,8,11,13,14 are coprime with 15. System.out.printf("phi(15) = %d\n", eulersTotient(15)); System.out.println(); - for (int x = 1; x <= 11; x++) { + for (int x = 1; x <= 11; x++) System.out.printf("phi(%d) = %d\n", x, eulersTotient(x)); - } } } diff --git a/src/test/java/com/williamfiset/algorithms/math/BUILD b/src/test/java/com/williamfiset/algorithms/math/BUILD index 5d3a10fab..12257dace 100644 --- a/src/test/java/com/williamfiset/algorithms/math/BUILD +++ b/src/test/java/com/williamfiset/algorithms/math/BUILD @@ -25,3 +25,14 @@ java_test( 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/EulerTotientFunctionTest.java b/src/test/java/com/williamfiset/algorithms/math/EulerTotientFunctionTest.java new file mode 100644 index 000000000..6e79357b4 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/math/EulerTotientFunctionTest.java @@ -0,0 +1,55 @@ +package com.williamfiset.algorithms.math; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.*; + +public class EulerTotientFunctionTest { + + // Known values: https://oeis.org/A000010 + private static final long[] EXPECTED = { + 0, 1, 1, 2, 2, 4, 2, 6, 4, 6, 4, 10, 4, 12, 6, 8, 8, 16, 6, 18, 8 + }; + + @Test + public void testKnownValues() { + for (int n = 1; n < EXPECTED.length; n++) + assertThat(EulerTotientFunction.eulersTotient(n)).isEqualTo(EXPECTED[n]); + } + + @Test + public void testOne() { + assertThat(EulerTotientFunction.eulersTotient(1)).isEqualTo(1); + } + + @Test + public void testPrimeReturnsPMinusOne() { + int[] primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 97}; + for (int p : primes) + assertThat(EulerTotientFunction.eulersTotient(p)).isEqualTo(p - 1); + } + + @Test + public void testPowerOfTwo() { + // phi(2^k) = 2^(k-1) + for (int k = 1; k <= 20; k++) + assertThat(EulerTotientFunction.eulersTotient(1L << k)).isEqualTo(1L << (k - 1)); + } + + @Test + public void testLargeValue() { + // phi(1000000) = 400000 + assertThat(EulerTotientFunction.eulersTotient(1000000)).isEqualTo(400000); + } + + @Test + public void testZeroThrows() { + assertThrows(IllegalArgumentException.class, () -> EulerTotientFunction.eulersTotient(0)); + } + + @Test + public void testNegativeThrows() { + assertThrows(IllegalArgumentException.class, () -> EulerTotientFunction.eulersTotient(-5)); + } +} From 5f708bcacf8c5307ba2ad1ab62dea1b172ee4adc Mon Sep 17 00:00:00 2001 From: William Fiset Date: Wed, 25 Mar 2026 09:49:50 -0700 Subject: [PATCH 4/8] Refactor ExtendedEuclideanAlgorithm and ModularInverse: add docs, clean up Co-Authored-By: Claude Opus 4.6 --- README.md | 2 +- .../math/ExtendedEuclideanAlgorithm.java | 46 +++++++++----- .../algorithms/math/ModularInverse.java | 61 ++++++++++--------- 3 files changed, 66 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 0566f010c..b39532253 100644 --- a/README.md +++ b/README.md @@ -242,7 +242,7 @@ $ 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 (phi function, relatively prime number count)](src/main/java/com/williamfiset/algorithms/math/EulerTotientFunction.java) **- O(√n)** - [Totient function using sieve (phi function, relatively prime number count)](src/main/java/com/williamfiset/algorithms/math/EulerTotientFunctionWithSieve.java) **- O(nlog(log(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))** diff --git a/src/main/java/com/williamfiset/algorithms/math/ExtendedEuclideanAlgorithm.java b/src/main/java/com/williamfiset/algorithms/math/ExtendedEuclideanAlgorithm.java index b0e863e17..73f28ee3c 100644 --- a/src/main/java/com/williamfiset/algorithms/math/ExtendedEuclideanAlgorithm.java +++ b/src/main/java/com/williamfiset/algorithms/math/ExtendedEuclideanAlgorithm.java @@ -1,21 +1,39 @@ -/** Time Complexity ~O(log(a + b)) */ +/** + * Extended Euclidean Algorithm. Given two integers a and b, computes gcd(a, b) and finds integers x + * and y such that ax + by = gcd(a, b). Useful for finding modular inverses and solving linear + * Diophantine equations. + * + *

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/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)); } } From ba8ae7ca777a377698259fe096ad81abe7875322 Mon Sep 17 00:00:00 2001 From: William Fiset Date: Wed, 25 Mar 2026 10:06:22 -0700 Subject: [PATCH 5/8] Refactor LCM: rename to Lcm, add docs, simplify Co-Authored-By: Claude Opus 4.6 --- README.md | 2 +- .../com/williamfiset/algorithms/math/BUILD | 6 ++-- .../com/williamfiset/algorithms/math/LCM.java | 29 ------------------ .../com/williamfiset/algorithms/math/Lcm.java | 30 +++++++++++++++++++ 4 files changed, 34 insertions(+), 33 deletions(-) delete mode 100644 src/main/java/com/williamfiset/algorithms/math/LCM.java create mode 100644 src/main/java/com/williamfiset/algorithms/math/Lcm.java diff --git a/README.md b/README.md index b39532253..d2d69e9cf 100644 --- a/README.md +++ b/README.md @@ -250,7 +250,7 @@ $ java -cp classes com.williamfiset.algorithms.search.BinarySearch - [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 (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 7c4352bcc..a207038e1 100644 --- a/src/main/java/com/williamfiset/algorithms/math/BUILD +++ b/src/main/java/com/williamfiset/algorithms/math/BUILD @@ -49,10 +49,10 @@ java_binary( 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/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 + } +} From d9872de76352ad784fbe88909c221cc4fb8396d9 Mon Sep 17 00:00:00 2001 From: William Fiset Date: Wed, 25 Mar 2026 10:15:06 -0700 Subject: [PATCH 6/8] =?UTF-8?q?Refactor=20IsPrime:=20rename=20to=20Primali?= =?UTF-8?q?tyCheck,=20add=20docs=20and=206k=C2=B11=20explanation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- README.md | 2 +- .../com/williamfiset/algorithms/math/BUILD | 6 +-- .../williamfiset/algorithms/math/IsPrime.java | 31 ------------ .../algorithms/math/PrimalityCheck.java | 50 +++++++++++++++++++ 4 files changed, 54 insertions(+), 35 deletions(-) delete mode 100644 src/main/java/com/williamfiset/algorithms/math/IsPrime.java create mode 100644 src/main/java/com/williamfiset/algorithms/math/PrimalityCheck.java diff --git a/README.md b/README.md index d2d69e9cf..4ead95480 100644 --- a/README.md +++ b/README.md @@ -248,7 +248,7 @@ $ java -cp classes com.williamfiset.algorithms.search.BinarySearch - [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))** - [Modular inverse](src/main/java/com/williamfiset/algorithms/math/ModularInverse.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 a207038e1..2989ab87a 100644 --- a/src/main/java/com/williamfiset/algorithms/math/BUILD +++ b/src/main/java/com/williamfiset/algorithms/math/BUILD @@ -42,10 +42,10 @@ java_binary( 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"], ) 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/PrimalityCheck.java b/src/main/java/com/williamfiset/algorithms/math/PrimalityCheck.java new file mode 100644 index 000000000..d2636ad8a --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/math/PrimalityCheck.java @@ -0,0 +1,50 @@ +/** + * 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: + * + *

+ *   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. + * + *

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; + } + + 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 + } +} From c2489a21659415eedceb1d30fe4e07e2e5c365f4 Mon Sep 17 00:00:00 2001 From: William Fiset Date: Wed, 25 Mar 2026 10:38:15 -0700 Subject: [PATCH 7/8] Add mod 30 wheel primality check to PrimalityCheck Co-Authored-By: Claude Opus 4.6 --- .../algorithms/math/PrimalityCheck.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/main/java/com/williamfiset/algorithms/math/PrimalityCheck.java b/src/main/java/com/williamfiset/algorithms/math/PrimalityCheck.java index d2636ad8a..ebb808b09 100644 --- a/src/main/java/com/williamfiset/algorithms/math/PrimalityCheck.java +++ b/src/main/java/com/williamfiset/algorithms/math/PrimalityCheck.java @@ -39,6 +39,37 @@ public static boolean isPrime(long n) { 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; + long limit = (long) Math.sqrt(n); + int[] offsets = {1, 7, 11, 13, 17, 19, 23, 29}; + for (long base = 0; base <= limit; base += 30) + for (int offset : offsets) { + long d = base + offset; + if (d < 7) + continue; + if (d > limit) + break; + 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 @@ -46,5 +77,14 @@ public static void main(String[] args) { 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 } } From b55346614b6e6970927bae8f180137e38362fc5d Mon Sep 17 00:00:00 2001 From: William Fiset Date: Wed, 25 Mar 2026 10:47:28 -0700 Subject: [PATCH 8/8] Math updates --- README.md | 1 - .../com/williamfiset/algorithms/math/BUILD | 7 -- .../math/EulerTotientFunctionWithSieve.java | 71 ------------------- .../algorithms/math/PrimalityCheck.java | 17 ++--- 4 files changed, 6 insertions(+), 90 deletions(-) delete mode 100644 src/main/java/com/williamfiset/algorithms/math/EulerTotientFunctionWithSieve.java diff --git a/README.md b/README.md index 4ead95480..30ff21991 100644 --- a/README.md +++ b/README.md @@ -243,7 +243,6 @@ $ java -cp classes com.williamfiset.algorithms.search.BinarySearch - [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(√n)** -- [Totient function using sieve (phi function, relatively prime number count)](src/main/java/com/williamfiset/algorithms/math/EulerTotientFunctionWithSieve.java) **- O(nlog(log(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))** - [Fast Fourier transform (quick polynomial multiplication)](src/main/java/com/williamfiset/algorithms/math/FastFourierTransform.java) **- O(nlog(n))** diff --git a/src/main/java/com/williamfiset/algorithms/math/BUILD b/src/main/java/com/williamfiset/algorithms/math/BUILD index 2989ab87a..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", diff --git a/src/main/java/com/williamfiset/algorithms/math/EulerTotientFunctionWithSieve.java b/src/main/java/com/williamfiset/algorithms/math/EulerTotientFunctionWithSieve.java deleted file mode 100644 index e845fecb4..000000000 --- a/src/main/java/com/williamfiset/algorithms/math/EulerTotientFunctionWithSieve.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Computes Euler's totient function - * - * @author Steven & Felix Halim - */ -package com.williamfiset.algorithms.math; - -public class EulerTotientFunctionWithSieve { - - // TODO(williamfiset): Refactor this class to accept a max value in the constructor. - - // Set MAX to be the largest value you - // wish to compute the totient for. - private static int MAX = 1000000; - private static int[] PRIMES = sieve(MAX); - - // Returns the value of Euler's totient/phi function - // which computes how many numbers are relativity - // prime to n less than or equal to n - public static int totient(int n) { - - if (n >= MAX - 1) throw new IllegalStateException("MAX not large enough!"); - int ans = n; - - for (int i = 1, p = PRIMES[0]; p * p <= n; i++) { - - if (n % p == 0) ans -= ans / p; - while (n % p == 0) n /= p; - p = PRIMES[i]; - } - - // Last factor - if (n != 1) ans -= ans / n; - return ans; - } - - // Gets all primes up to, but NOT including limit (returned as a list of primes) - private static int[] sieve(int limit) { - - if (limit <= 2) return new int[0]; - - // Find an upper bound on the number of primes below our limit. - // https://en.wikipedia.org/wiki/Prime-counting_function#Inequalities - final int numPrimes = (int) (1.25506 * limit / Math.log((double) limit)); - int[] primes = new int[numPrimes]; - int index = 0; - - boolean[] isComposite = new boolean[limit]; - final int sqrtLimit = (int) Math.sqrt(limit); - for (int i = 2; i <= sqrtLimit; i++) { - if (!isComposite[i]) { - primes[index++] = i; - for (int j = i * i; j < limit; j += i) isComposite[j] = true; - } - } - for (int i = sqrtLimit + 1; i < limit; i++) if (!isComposite[i]) primes[index++] = i; - return java.util.Arrays.copyOf(primes, index); - } - - public static void main(String[] args) { - // Prints 8 because 1,2,4,7,8,11,13,14 are all - // less than 15 and relatively prime with 15 - System.out.printf("phi(15) = %d\n", totient(15)); - - System.out.println(); - - for (int x = 1; x <= 11; x++) { - System.out.printf("phi(%d) = %d\n", x, totient(x)); - } - } -} diff --git a/src/main/java/com/williamfiset/algorithms/math/PrimalityCheck.java b/src/main/java/com/williamfiset/algorithms/math/PrimalityCheck.java index ebb808b09..63b9b70a7 100644 --- a/src/main/java/com/williamfiset/algorithms/math/PrimalityCheck.java +++ b/src/main/java/com/williamfiset/algorithms/math/PrimalityCheck.java @@ -55,18 +55,13 @@ public static boolean isPrimeWheel30(long n) { 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[] offsets = {1, 7, 11, 13, 17, 19, 23, 29}; - for (long base = 0; base <= limit; base += 30) - for (int offset : offsets) { - long d = base + offset; - if (d < 7) - continue; - if (d > limit) - break; - if (n % d == 0) - return false; - } + int gi = 0; + for (long d = 7; d <= limit; d += gaps[gi++ % 8]) + if (n % d == 0) + return false; return true; }