I want to note what happens if you add "Wheel Factorization" to Oscar Smith's answer.
Wheel Factorization
Wheel Factorization requires some basis primes (e.g $\{2, 3\}$)
We'll call the product of these basis primes: $p$ (e.g $2 \cdot 3 = 6$)
We now organize numbers in a table with $p$ columns, and eliminate the following numbers:
- Any number in the first row, divisible by a basis numer:
- Any number under our basis numbers or eliminated numbers:
Wheel Factorization doesn't remove all composites, there are still some composites in the list.
Packing algorithm
We need to save the first row, but everything after the first row can be compressed nicely:
7 11 Yes Yes 11
13 17 -> Yes Yes -> 11
19 23 Yes Yes 11
25 29 No Yes 01
...
We also need to save which numbers were eliminated, which can again be done bitwise, we'll call this our header:
7 # # # 11 #
|
v
header = 0 1 1 1 0 1
Unpacking algorithm
To pinpoint a number like $25$, we compute which row & column it's in:
$$r = \lfloor(25 - 1) / 6\rfloor = \color{red}{4}$$
$$c = ((25 - 1) / 6 - r) \cdot 6 = \color{blue}{0}$$
We then check our $c$th ($\color{blue}{0}$th) bit in our header. If it's eliminated (the bit is flipped on to 1), then we know it's not a prime. In this case the bit is 0, meaning we have to look further in the table.
We check the first item in the $r$th ($\color{red}{4}$th) row, and find it's a $0$, finally telling us it's composite:
11 7 11
11 13 17
11 19 23
> 01 -> 25 29
.. ...
I've brushed over some details, which aren't really important unless your actually implementing this:
You need to save the first row for numbers $\leq p$. You'll also need to know how many 0s came before the $c$th bit in the header. In the example there were no 0s before the $c$th bit meaning we had to "check the first item".
Total size
To save the primality lookup table up to $N$ for a basis set of primes $B = \{b_1, b_2, b_3, \dots, b_n \}$ we need:
- Header with $p$ bits, where $p = \prod^{n}_{i=0} b_i$
- The first row, or primality of all numbers $\leq p$, which is another $p$ bits.
- Everything after the first row:
- Let $k$ be number of columns that aren't eliminated, or:
$$k = |\{m \in \mathbb{N}/\{0\}\ |\ m \leq p \land \forall b \in B(m \text{ is not divisible by } b) \}|$$
- Then everything after the first row takes: $k \lfloor N/p \rfloor$ bits (since $N/p$ is how many rows there are).
Which totals to: $2p + k\lfloor N/p \rfloor$ bits. We can find how many integers you can fit in 30KB by solving for $N$:
- If $B = \{2, 3\}$, then $p = 6, k = 2$
- $N=737,244$ fits in 30KB (245,760 bits).
- If $B = \{2, 3, 5, 7, 11\}$, then $p = 2,310, k = 480$
- $N=1,160,486$ fits in 30KB.
- If $B = \{2, 3, 5, 7, 11, 13\}$, then $p = 30,030, k= 5,760$
- $N = 960,960$ fits in 30KB.
Conclusion
So the best basis you can get with this particular "Wheel Factorization" approach is $\{2, 3, 5, 7, 11\}$. Which can store up to 1,160,486 integers.
Any larger basis will cause $N$ to decrease. This is because the header & first row are uncompressed, meaning the header and first row will eventually become larger than 30KB.
If you move the header and first row into the program, you won't have this limit, but you'll be storing quite a lot in the program. For some perspective, here are some $p,k$ values:
p k M = (245760p)/k
30030 5760 1281280
510510 92160 1361360
9699690 1658880 1436991
340510170 56770560 1474070
...
You'll be storing $2p$ bits in the program, to store the first $M$ integers. After $p=510,510$, it seems to become impractical.
TL;DR: This particular Wheel Factorization approach can only save ~5 integers per bit.