The classic approach at generating primes less than a certain value \( N \) is the Sieve of Eratosthenes. The algorithm simply proceeds by dropping all the numbers generated by repeatedly adding a number (thus multiples of the first number).

#include <iostream>
#include <cmath>
#include <vector>
using namespace std;

void printPrimes(unsigned int N) {
  // Generate all prime numbers less than or equal to N

  vector<bool> isPrime(N + 1, true);
  isPrime[0] = false;
  isPrime[1] = false; // By definition

  for (unsigned int i = 4; i<isPrime.size(); i += 2) {
    isPrime[i] = false;
  }
  unsigned int sqr = static_cast<unsigned int>(sqrt(N));
  for (unsigned int i = 3; i <= sqr; i += 2) {
    unsigned int val = i + i;
    while (val < isPrime.size()) {
      isPrime[val] = false;
      val += i;
    }
  }
  for (unsigned int i = 0; i <= N; ++i) {
    if (isPrime[i] == true)
      cout << i << " ";
  }
}

int main() {
  const int N = 100;
  printPrimes(N);
  return 0;
}

The code above features two optimization:

  • Even numbers are computed beforehand while odd numbers proceed skipping the even ones
  • Only numbers below \( \sqrt{N} \) are considered since any other greater number would be useless to compute by recursively adding it multiple times

A quick computational analysis shows that the running time is

\[\frac{N}{2} + \frac{N}{3} + \frac{N}{5} + \ldots + \frac{N}{N} = N(\frac{1}{2} + \frac{1}{3} + \frac{1}{5} + \ldots + \frac{1}{N})\]

and since by harmonic number

\[\int_0^N\frac{1}{x}\,dx\ = \log{x} + c\]

we can conclude that a loose upper-bound is \( O(N\log{N}) \) (a better analysis would yield \( O(N\log{\log{N}}) \)).

Interval prime generation using \( 6k\pm1 \)

If we’re prompted with the task of generating primes in a given interval \( [x;y] \) with \( x \) and \( y \) potentially large integers but close to each other (i.e. a small interval on a high-range,) a faster approach could be exploiting the \( 6k\pm1 \) property (a variation on Euclid’s proof of infinite primes). The argument shows that any prime \( P \) is either of the form \( 6k+1 \) or \( 6k-1 \).

A prime number greater than 3 cannot have the following forms:

  • \( 6k \) since it would be divisible by 2 and 3
  • \( 6k\pm2 \) since it would be divisible by 2
  • \( 6k\pm3 \) since it would be divisible by 3
  • \( 6k\pm4 \) since it would be divisible by 2

so it is either of the form \( 6k\pm1 \) or \( 6k\pm5 \). The latter form is equivalent to the first one since increasing the index \( k \) yields \( 6(k+1)-5 = 6k+1 \). The same holds for \( 6k-1 \).

Therefore if we need to generate primes into a given interval we can simply reach a \( 6k\pm1 \) number in that interval and proceed with the primality testing. Testing for a \( 6k\pm1 \) number to be a prime is a necessary check since all primes are of the form \( 6k\pm1 \) but not all \( 6k\pm1 \) numbers are prime (e.g. \( 6k-1 \) with \( k=6 \)).

bool isPrime(unsigned int num) {
  if (num == 0 || num == 1)
    return false;
  if (num == 2 || num == 3)
    return true;
  unsigned int sq = sqrt(num);
  if (num % 2 == 0)
    return false;
  if (sq % 2 == 0)
    --sq;
  for (unsigned int i = sq; i >= 2; i -= 2) {
    if (num % i == 0)
      return false;
  }
  return true;
}

void printPrimesInInterval(unsigned int A, unsigned int B) {
  if (2 >= A && 2 <= B)
    cout << 2 << endl;
  if (3 >= A && 3 <= B)
    cout << 3 << endl;

  for (int i = 1;; ++i) {
    unsigned int value = 6 * i;
    if (value - 1 >= A && value - 1 <= B) {
      if (isPrime(value - 1))
        cout << value - 1 << endl;
    } else if (value - 1 > B)
      break;
    if (value + 1 >= A && value + 1 <= B) {
      if (isPrime(value + 1))
        cout << value + 1 << endl;
    }
  }
}

The code features many optimization spots and has been kept overly verbose for readability purposes. Complexity is \( O((y-x)\sqrt{y}) \) being the primality test the most expensive operation.