Skip to content

Commit a1306d8

Browse files
williamfisetclaude
andcommitted
Refactor EulerTotientFunction: simplify with trial division, add tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8f36c11 commit a1306d8

3 files changed

Lines changed: 98 additions & 64 deletions

File tree

Lines changed: 32 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,49 @@
1+
/**
2+
* Computes Euler's totient function phi(n), which counts the number of integers in [1, n] that are
3+
* relatively prime to n.
4+
*
5+
* <p>Uses trial division to find prime factors and applies the product formula:
6+
* phi(n) = n * product of (1 - 1/p) for each distinct prime factor p of n.
7+
*
8+
* <p>Time: O(sqrt(n))
9+
*
10+
* @author William Fiset, william.alexandre.fiset@gmail.com
11+
*/
112
package com.williamfiset.algorithms.math;
213

3-
import java.util.*;
4-
514
public class EulerTotientFunction {
615

16+
/**
17+
* Computes Euler's totient phi(n).
18+
*
19+
* @param n a positive integer.
20+
* @return the number of integers in [1, n] that are coprime to n.
21+
* @throws IllegalArgumentException if n is not positive.
22+
*/
723
public static long eulersTotient(long n) {
8-
for (long p : new HashSet<Long>(primeFactorization(n))) n -= (n / p);
9-
return n;
10-
}
11-
12-
private static ArrayList<Long> primeFactorization(long n) {
13-
ArrayList<Long> factors = new ArrayList<Long>();
14-
if (n <= 0) throw new IllegalArgumentException();
15-
else if (n == 1) return factors;
16-
PriorityQueue<Long> divisorQueue = new PriorityQueue<Long>();
17-
divisorQueue.add(n);
18-
while (!divisorQueue.isEmpty()) {
19-
long divisor = divisorQueue.remove();
20-
if (isPrime(divisor)) {
21-
factors.add(divisor);
22-
continue;
23-
}
24-
long next_divisor = pollardRho(divisor);
25-
if (next_divisor == divisor) {
26-
divisorQueue.add(divisor);
27-
} else {
28-
divisorQueue.add(next_divisor);
29-
divisorQueue.add(divisor / next_divisor);
24+
if (n <= 0)
25+
throw new IllegalArgumentException("n must be positive.");
26+
long result = n;
27+
for (long p = 2; p * p <= n; p++) {
28+
if (n % p == 0) {
29+
while (n % p == 0)
30+
n /= p;
31+
result -= result / p;
3032
}
3133
}
32-
return factors;
33-
}
34-
35-
private static long pollardRho(long n) {
36-
if (n % 2 == 0) return 2;
37-
// Get a number in the range [2, 10^6]
38-
long x = 2 + (long) (999999 * Math.random());
39-
long c = 2 + (long) (999999 * Math.random());
40-
long y = x;
41-
long d = 1;
42-
while (d == 1) {
43-
x = (x * x + c) % n;
44-
y = (y * y + c) % n;
45-
y = (y * y + c) % n;
46-
d = gcf(Math.abs(x - y), n);
47-
if (d == n) break;
48-
}
49-
return d;
50-
}
51-
52-
private static long gcf(long a, long b) {
53-
return b == 0 ? a : gcf(b, a % b);
54-
}
55-
56-
private static boolean isPrime(long n) {
57-
58-
if (n < 2) return false;
59-
if (n == 2 || n == 3) return true;
60-
if (n % 2 == 0 || n % 3 == 0) return false;
61-
62-
int limit = (int) Math.sqrt(n);
63-
64-
for (int i = 5; i <= limit; i += 6) if (n % i == 0 || n % (i + 2) == 0) return false;
65-
66-
return true;
34+
// If n still has a prime factor greater than sqrt(original n).
35+
if (n > 1)
36+
result -= result / n;
37+
return result;
6738
}
6839

6940
public static void main(String[] args) {
70-
71-
// Prints 8 because 1,2,4,7,8,11,13,14 are all
72-
// less than 15 and relatively prime with 15
41+
// phi(15) = 8 because 1,2,4,7,8,11,13,14 are coprime with 15.
7342
System.out.printf("phi(15) = %d\n", eulersTotient(15));
7443

7544
System.out.println();
7645

77-
for (int x = 1; x <= 11; x++) {
46+
for (int x = 1; x <= 11; x++)
7847
System.out.printf("phi(%d) = %d\n", x, eulersTotient(x));
79-
}
8048
}
8149
}

src/test/java/com/williamfiset/algorithms/math/BUILD

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,14 @@ java_test(
2525
runtime_deps = JUNIT5_RUNTIME_DEPS,
2626
deps = TEST_DEPS,
2727
)
28+
29+
# bazel test //src/test/java/com/williamfiset/algorithms/math:EulerTotientFunctionTest
30+
java_test(
31+
name = "EulerTotientFunctionTest",
32+
srcs = ["EulerTotientFunctionTest.java"],
33+
main_class = "org.junit.platform.console.ConsoleLauncher",
34+
use_testrunner = False,
35+
args = ["--select-class=com.williamfiset.algorithms.math.EulerTotientFunctionTest"],
36+
runtime_deps = JUNIT5_RUNTIME_DEPS,
37+
deps = TEST_DEPS,
38+
)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.williamfiset.algorithms.math;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import org.junit.jupiter.api.*;
7+
8+
public class EulerTotientFunctionTest {
9+
10+
// Known values: https://oeis.org/A000010
11+
private static final long[] EXPECTED = {
12+
0, 1, 1, 2, 2, 4, 2, 6, 4, 6, 4, 10, 4, 12, 6, 8, 8, 16, 6, 18, 8
13+
};
14+
15+
@Test
16+
public void testKnownValues() {
17+
for (int n = 1; n < EXPECTED.length; n++)
18+
assertThat(EulerTotientFunction.eulersTotient(n)).isEqualTo(EXPECTED[n]);
19+
}
20+
21+
@Test
22+
public void testOne() {
23+
assertThat(EulerTotientFunction.eulersTotient(1)).isEqualTo(1);
24+
}
25+
26+
@Test
27+
public void testPrimeReturnsPMinusOne() {
28+
int[] primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 97};
29+
for (int p : primes)
30+
assertThat(EulerTotientFunction.eulersTotient(p)).isEqualTo(p - 1);
31+
}
32+
33+
@Test
34+
public void testPowerOfTwo() {
35+
// phi(2^k) = 2^(k-1)
36+
for (int k = 1; k <= 20; k++)
37+
assertThat(EulerTotientFunction.eulersTotient(1L << k)).isEqualTo(1L << (k - 1));
38+
}
39+
40+
@Test
41+
public void testLargeValue() {
42+
// phi(1000000) = 400000
43+
assertThat(EulerTotientFunction.eulersTotient(1000000)).isEqualTo(400000);
44+
}
45+
46+
@Test
47+
public void testZeroThrows() {
48+
assertThrows(IllegalArgumentException.class, () -> EulerTotientFunction.eulersTotient(0));
49+
}
50+
51+
@Test
52+
public void testNegativeThrows() {
53+
assertThrows(IllegalArgumentException.class, () -> EulerTotientFunction.eulersTotient(-5));
54+
}
55+
}

0 commit comments

Comments
 (0)