There are several optimizations can be done while caluculating prime numbers:
Not every Number is Prime
The only even number among primes is 2
(so we can eliminate all even numbers apart from 2
). And there's no gap between the prime, which is 3
.
I.e. we can omit 4
of every 6
consecutive numbers, without checking them. I.e. candidate number can be represented as N * 6 - 1
and N * 6 + 1
, which are guaranteed not to be divisible neither by 2
, no by 3
.
Use square root as a boundary
If the number is prime, it's fruitless to check it against numbers greater than its square root (see elaborate explanation here).
Here's how isPrime()
check can be implemented taking into consideration both optimizations listed above
public static boolean isPrime(int candidate) {
if (candidate == 2 || candidate == 3) return true;
if (candidate % 2 == 0 || candidate % 3 == 0) return false;
boolean isPrime = true;
for (int i = 6; i <= Math.sqrt(candidate); i += 6) {
if (candidate % (i - 1) == 0 || candidate % (i + 1) == 0) {
isPrime = false;
break;
}
}
return candidate > 1 && isPrime;
}
Don't recalculate the same Primes
Validate the potential prime number against the primes that has been already discovered.
Here's my implementation created based on all the principles listed above, which allow to calculating primes in the given range in a highly performant manner encapsulated into a separate utility class.
public class PrimesGenerator {
private final List<Integer> primes;
private final int loBound;
private final int hiBound;
private PrimesGenerator(int loBound, int hiBound) {
this.primes = new ArrayList<>();
this.loBound = loBound;
this.hiBound = hiBound;
}
public static PrimesGenerator getInstance(int loBound, int hiBound) {
PrimesGenerator generator = new PrimesGenerator(max(loBound, 2), hiBound);
generator.init();
return generator;
}
private void init() {
if (loBound <= 2) primes.add(2);
if (loBound <= 3) primes.add(3);
int candidate = 5;
while (candidate <= hiBound) {
if (isPrime(candidate)) {
primes.add(candidate);
}
candidate = getNextCandidate(candidate);
}
}
private boolean isPrime(int candidate) {
boolean isPrime = true;
double sqrt = sqrt(candidate);
for (int i = 0; i < primes.size() && primes.get(i) <= sqrt; i++) {
if (candidate % primes.get(i) == 0) {
isPrime = false;
break;
}
}
return isPrime;
}
private int getNextCandidate(int candidate) {
return candidate % 6 == 5 ? candidate + 2 : candidate + 4;
}
public List<Integer> getResult() {
return primes.stream()
.dropWhile(i -> i < loBound)
.collect(toUnmodifiableList());
}
}
Usage example:
public static void main(String[] args) {
PrimesGenerator generator = PrimesGenerator.getInstance(2,100);
List<Integer> primes = generator.getResult();
System.out.println(primes);
}
Output:
[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]