Skip to content

Commit 8f36c11

Browse files
williamfisetclaude
andcommitted
Refactor CompressedPrimeSieve: add docs, clean up, add tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 785002a commit 8f36c11

3 files changed

Lines changed: 135 additions & 37 deletions

File tree

Lines changed: 31 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,70 @@
11
/**
2-
* Generate a compressed prime sieve using bit manipulation. The idea is that each bit represents a
3-
* boolean value indicating whether a number is prime or not. This saves a lot of room when creating
4-
* the sieve. In this implementation I store all odd numbers in individual longs meaning that for
5-
* each long I use I can represent a range of 128 numbers (even numbers are omitted because they are
6-
* not prime, with the exception of 2 which is handled as a special case).
2+
* Generates a compressed prime sieve using bit manipulation. Each bit represents whether an odd
3+
* number is prime or not. Even numbers are omitted (except 2, handled as a special case), so each
4+
* long covers a range of 128 numbers.
75
*
8-
* <p>Time Complexity: ~O(nloglogn)
6+
* <p>Time: ~O(n log(log(n)))
97
*
10-
* <p>Compile: javac -d src/main/java
11-
* src/main/java/com/williamfiset/algorithms/math/CompressedPrimeSieve.java
12-
*
13-
* <p>Run: java -cp src/main/java com/williamfiset/algorithms/math/CompressedPrimeSieve
8+
* <p>Space: O(n / 128) longs
149
*
1510
* @author William Fiset, william.alexandre.fiset@gmail.com
1611
*/
1712
package com.williamfiset.algorithms.math;
1813

1914
public class CompressedPrimeSieve {
15+
2016
private static final double NUM_BITS = 128.0;
2117
private static final int NUM_BITS_SHIFT = 7; // 2^7 = 128
2218

23-
// Sets the bit representing n to 1 indicating this number is not prime
19+
// Marks n as not prime by setting its bit to 1.
2420
private static void setBit(long[] arr, int n) {
25-
if ((n & 1) == 0) return; // n is even
21+
if ((n & 1) == 0)
22+
return;
2623
arr[n >> NUM_BITS_SHIFT] |= 1L << ((n - 1) >> 1);
2724
}
2825

29-
// Returns true if the bit for n is off (meaning n is a prime).
30-
// Note: do use this method to access numbers outside your prime sieve range!
26+
// Returns true if n's bit is unset (meaning n is prime).
3127
private static boolean isNotSet(long[] arr, int n) {
32-
if (n < 2) return false; // n is not prime
33-
if (n == 2) return true; // two is prime
34-
if ((n & 1) == 0) return false; // n is even
28+
if (n < 2)
29+
return false;
30+
if (n == 2)
31+
return true;
32+
if ((n & 1) == 0)
33+
return false;
3534
long chunk = arr[n >> NUM_BITS_SHIFT];
3635
long mask = 1L << ((n - 1) >> 1);
3736
return (chunk & mask) != mask;
3837
}
3938

40-
// Returns true/false depending on whether n is prime.
39+
/** Returns true if n is prime according to the given sieve. */
4140
public static boolean isPrime(long[] sieve, int n) {
4241
return isNotSet(sieve, n);
4342
}
4443

45-
// Returns an array of longs with each bit indicating whether a number
46-
// is prime or not. Use the isNotSet and setBit methods to toggle to bits for each number.
44+
/**
45+
* Builds a compressed prime sieve for all numbers up to {@code limit}.
46+
*
47+
* @param limit the upper bound (inclusive) for the sieve.
48+
* @return a bit-packed array where each bit indicates whether an odd number is composite.
49+
*/
4750
public static long[] primeSieve(int limit) {
48-
final int numChunks = (int) Math.ceil(limit / NUM_BITS);
49-
final int sqrtLimit = (int) Math.sqrt(limit);
50-
// if (limit < 2) return 0; // uncomment for primeCount purposes
51-
// int primeCount = (int) Math.ceil(limit / 2.0); // Counts number of primes <= limit
51+
int numChunks = (int) Math.ceil(limit / NUM_BITS);
52+
int sqrtLimit = (int) Math.sqrt(limit);
5253
long[] chunks = new long[numChunks];
53-
chunks[0] = 1; // 1 as not prime
54+
chunks[0] = 1; // Mark 1 as not prime.
5455
for (int i = 3; i <= sqrtLimit; i += 2)
5556
if (isNotSet(chunks, i))
5657
for (int j = i * i; j <= limit; j += i)
57-
if (isNotSet(chunks, j)) {
58+
if (isNotSet(chunks, j))
5859
setBit(chunks, j);
59-
// primeCount--;
60-
}
6160
return chunks;
6261
}
6362

64-
/* Example usage. */
65-
6663
public static void main(String[] args) {
67-
final int limit = 200;
68-
long[] sieve = CompressedPrimeSieve.primeSieve(limit);
69-
70-
for (int i = 0; i <= limit; i++) {
71-
if (CompressedPrimeSieve.isPrime(sieve, i)) {
64+
int limit = 200;
65+
long[] sieve = primeSieve(limit);
66+
for (int i = 0; i <= limit; i++)
67+
if (isPrime(sieve, i))
7268
System.out.printf("%d is prime!\n", i);
73-
}
74-
}
7569
}
7670
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
load("@rules_java//java:defs.bzl", "java_test")
2+
3+
# Common dependencies for JUnit 5 tests
4+
JUNIT5_DEPS = [
5+
"@maven//:org_junit_jupiter_junit_jupiter_api",
6+
"@maven//:org_junit_jupiter_junit_jupiter_engine",
7+
]
8+
9+
JUNIT5_RUNTIME_DEPS = [
10+
"@maven//:org_junit_platform_junit_platform_console",
11+
]
12+
13+
TEST_DEPS = [
14+
"//src/main/java/com/williamfiset/algorithms/math:math",
15+
"@maven//:com_google_truth_truth",
16+
] + JUNIT5_DEPS
17+
18+
# bazel test //src/test/java/com/williamfiset/algorithms/math:CompressedPrimeSieveTest
19+
java_test(
20+
name = "CompressedPrimeSieveTest",
21+
srcs = ["CompressedPrimeSieveTest.java"],
22+
main_class = "org.junit.platform.console.ConsoleLauncher",
23+
use_testrunner = False,
24+
args = ["--select-class=com.williamfiset.algorithms.math.CompressedPrimeSieveTest"],
25+
runtime_deps = JUNIT5_RUNTIME_DEPS,
26+
deps = TEST_DEPS,
27+
)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.williamfiset.algorithms.math;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
5+
import org.junit.jupiter.api.*;
6+
7+
public class CompressedPrimeSieveTest {
8+
9+
// First 25 primes (all primes <= 100).
10+
private static final int[] PRIMES_UP_TO_100 = {
11+
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89,
12+
97
13+
};
14+
15+
@Test
16+
public void testPrimesUpTo100() {
17+
long[] sieve = CompressedPrimeSieve.primeSieve(100);
18+
java.util.Set<Integer> expected = new java.util.HashSet<>();
19+
for (int p : PRIMES_UP_TO_100)
20+
expected.add(p);
21+
22+
for (int i = 0; i <= 100; i++) {
23+
if (expected.contains(i))
24+
assertThat(CompressedPrimeSieve.isPrime(sieve, i)).isTrue();
25+
else
26+
assertThat(CompressedPrimeSieve.isPrime(sieve, i)).isFalse();
27+
}
28+
}
29+
30+
@Test
31+
public void testZeroAndOneAreNotPrime() {
32+
long[] sieve = CompressedPrimeSieve.primeSieve(10);
33+
assertThat(CompressedPrimeSieve.isPrime(sieve, 0)).isFalse();
34+
assertThat(CompressedPrimeSieve.isPrime(sieve, 1)).isFalse();
35+
}
36+
37+
@Test
38+
public void testTwoIsPrime() {
39+
long[] sieve = CompressedPrimeSieve.primeSieve(10);
40+
assertThat(CompressedPrimeSieve.isPrime(sieve, 2)).isTrue();
41+
}
42+
43+
@Test
44+
public void testEvenNumbersAreNotPrime() {
45+
long[] sieve = CompressedPrimeSieve.primeSieve(200);
46+
for (int i = 4; i <= 200; i += 2)
47+
assertThat(CompressedPrimeSieve.isPrime(sieve, i)).isFalse();
48+
}
49+
50+
@Test
51+
public void testPrimeCount() {
52+
// There are 168 primes <= 1000.
53+
long[] sieve = CompressedPrimeSieve.primeSieve(1000);
54+
int count = 0;
55+
for (int i = 0; i <= 1000; i++)
56+
if (CompressedPrimeSieve.isPrime(sieve, i))
57+
count++;
58+
assertThat(count).isEqualTo(168);
59+
}
60+
61+
@Test
62+
public void testLargePrimes() {
63+
long[] sieve = CompressedPrimeSieve.primeSieve(10000);
64+
// Some known primes near the upper range.
65+
assertThat(CompressedPrimeSieve.isPrime(sieve, 9973)).isTrue();
66+
assertThat(CompressedPrimeSieve.isPrime(sieve, 9967)).isTrue();
67+
// Some known composites near the upper range.
68+
assertThat(CompressedPrimeSieve.isPrime(sieve, 9999)).isFalse();
69+
assertThat(CompressedPrimeSieve.isPrime(sieve, 10000)).isFalse();
70+
}
71+
72+
@Test
73+
public void testSmallLimit() {
74+
long[] sieve = CompressedPrimeSieve.primeSieve(2);
75+
assertThat(CompressedPrimeSieve.isPrime(sieve, 2)).isTrue();
76+
}
77+
}

0 commit comments

Comments
 (0)