Consider the following pseudocode for the Sieve of Eratosthenes, giving us the primes up to $N$:
1) List the numbers $2$ to $N$.
2) Let $p=2$.
2) Cross out $p^2$, then cross out $(p+1)p, (p+2)p, (p+3)p,..., (p+j)p$, where $j$ is the smallest integer such that $(p+j+1)p > N$.
3) Set $p$ equal to the first number in the list which isn't crossed out. If $p^2 > N$, then we stop, otherwise, go to step 2)
If you follow the above algorithm, then the numbers which are crossed out are the composite numbers between $2$ and $N$, and the remaining numbers are prime. I understand why this works, and why it's reasonably fast (because step 2 can be achieved only with one squaring and $j$ additions). This is pretty much the standard sieve of Eratosthenes, which you can find in various textbooks, as well as Wikipedia and Wolfram Mathworld.
However, I've recently started reading "Prime Numbers - A Computational Perspective" by Crandall and Pomerance. This is an excellent textbook, but I'm confused about Algorithm 3.2.1, which they call the "Practical Eratosthenes sieve" (page 122 in the second edition). I can see the resemblance to the algorithm above, but I don't understand exactly what's going on, why certain restrictions are assumed, and why it works. I've copied the algorithm (almost) verbatim here (I've clarified some of the ambiguous formatting). It is written in a fairly standard, C-style syntax:
"Algorithm 3.2.1 (Practical Eratosthenes sieve).
This algorithm finds all primes in an interval $(L, R)$ by establishing Boolean primality bits for successive runs of $B$ odd numbers. We assume $L$, $R$ even, with $R>L$, $B | R − L$ and $L> P = \lceil{\sqrt{R}}\rceil$. We also assume the availability of a table of the $\pi(P)$ primes $pk ≤ P$.
1) [Initialize the offsets]
for($k \in [2, \pi(P)]$) {
$\quad q_k = (-\frac{1}{2}(L +1+ p_k)) \bmod p_k$;
}
2) [Process blocks]
$T = L$;
while($T <R$) {
$\quad$for($j \in [0, B − 1]$) {
$\quad\quad b_j = 1$;
$\quad$}
$\quad$for($k \in [2, \pi(P)]$) {
$\quad\quad$for($j = q_k$; $j<B$; $j = j + p_k$) {
$\quad\quad\quad b_j = 0$;
$\quad\quad$}
$\quad\quad q_k = (q_k − B) \bmod p_k$;
$\quad$}
$\quad$for($j \in [0, B − 1]$) {
$\quad\quad$if($b_j$ == 1) report $T + 2j + 1$; // Output the prime $p = T + 2j + 1$.
$\quad$}
$\quad T = T + 2B$;
}"
(I apologise for my terrible formatting. I couldn't think of a better way of doing it.)
1) What is the significance of $B$? Is it just to lower memory requirements? Is there a "sweet spot" for choosing a value of $B$? Obviously, we could choose $B=1$ or $B = R-L$, but what should we consider when choosing a value of $B$? Additionally, why do we need $B | R - L$?
2) Why do we need $L > \lceil{\sqrt{R}}\rceil$?
3) What are the $q_k$ for? Why is their initial value set to $q_k = -(L + 1 + p_k)/2 \bmod p_k$?
4) What is happening in the main while loop? I can see that by setting the $b_j = 0$, we're "crossing out" composites as before, but I don't understand any of the logic (why increment $j$ by $p_k$ each time? Why set $q_k = (q_k - B) \bmod p_k$? Why set $T = T + 2B$?).
I'm sorry if this is a terribly formed question, but I don't even know where to begin with this. To me, it only slightly resembles the naive sieve of Eratosthenes mentioned at the start of my post, and I don't see why the Algorithm 3.2.1 should be more efficient or more appropriate.
I've asked a lot, but if anyone can tackle any of my queries, it'd be massively appreciated.