1 /++
2     This module contains utilities relating to prime numbers.
3 
4     Right now this module mainly has an infinite range of primes backed by a
5     Sieve of Eratosthenes.
6     
7     HELP_WELCOMED:
8     The potential number of "prime number utilities" is much, much larger than
9     the relatively small set here, that I've just wanted for some minor
10     benchmarks, but please feel free to submit additional utilities so the
11     namespace can become more useful.
12 +/
13 module math.primes;
14 
15 import std.traits : isIntegral;
16 
17 /++
18     Calculates the upper bound of the integer range containing the nth prime.
19 
20     Useful for knowing how large of a sieve to prepare if you want to collect a
21     certain number of prime numbers, for example.
22 +/
23 size_t nthPrimeUpperBound(size_t n) @nogc @safe pure nothrow {
24     import std.math : log;
25 
26     if (n > 6)
27         return cast(size_t)(n * log(n) + n * log(log(n)));
28     else
29         return 11;
30 }
31 
32 ///
33 unittest {
34     assert(nthPrimeUpperBound(4) == 11);
35     assert(nthPrimeUpperBound(7) == 18);
36     assert(nthPrimeUpperBound(50) == 263);
37 }
38 
39 /++
40     The Sieve of Eratosthenes
41 
42     Backed by a bool[]; the template argument is only used for integral type
43     yielded by the range.
44 +/
45 struct Sieve(T = size_t) if (isIntegral!T) {
46     private bool[] sieve; /// false = prime. ignore indexes < 2
47 
48     /// Constructs a sieve with the given size.
49     this(size_t length) @safe nothrow {
50         sieve.length = length;
51 
52         // code duplication with sift, but avoids unnecessary division
53         foreach (i; 2 .. length / 2) {
54             if (!sieve[i]) {
55                 size_t j = i + i;
56                 while (j < length) {
57                     sieve[j] = true;
58                     j += i;
59                 }
60             }
61         }
62     }
63 
64     /++
65         Loop over the entire sieve, but only start eliminating composite
66         numbers from after the 'from' index on. Useful after resizing the
67         sieve.
68     +/
69     private void sift(size_t from) @nogc @safe nothrow {
70         immutable size_t length = sieve.length;
71         foreach (i; 2 .. length / 2) {
72             if (!sieve[i]) {
73                 size_t j = i >= from ? i + i : from - from % i + i;
74                 while (j < length) {
75                     sieve[j] = true;
76                     j += i;
77                 }
78             }
79         }
80     }
81 
82     private size_t i = 2;
83 
84     bool empty = false;
85 
86     T front() @nogc @safe nothrow {
87         return cast(T) i;
88     }
89 
90     void popFront() @safe nothrow {
91         while (1) {
92             while (++i < sieve.length) {
93                 if (!sieve[i])
94                     return;
95             }
96             --i;
97             // ran out of sieve!
98             sieve.length += 5000;
99             sift(i - 1);
100         }
101     }
102 
103     /// Return a finite slice of the sieve.
104     auto fixed() @nogc @safe nothrow {
105         struct FixedSieve {
106             private bool[] sieve;
107             private size_t i = 2;
108 
109             bool empty() {
110                 return i >= sieve.length;
111             }
112 
113             T front() {
114                 return cast(T) i;
115             }
116 
117             void popFront() {
118                 while (++i < sieve.length) {
119                     if (!sieve[i])
120                         break;
121                 }
122             }
123         }
124 
125         return FixedSieve(sieve);
126     }
127 }
128 
129 ///
130 unittest {
131     import std.algorithm : sum, until;
132     import std.range : take, array;
133 
134     assert(Sieve!int(50).until!(n => n >= 50).sum == 328);
135     assert(Sieve!int(nthPrimeUpperBound(50)).take(50).sum == 5117);
136     assert(Sieve!int(30).fixed.array == [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]);
137     assert(Sieve!int(0).fixed.array == []);
138 
139     // this causes compilation to take about 9s and 2 GB of memory.
140     // static assert(Sieve!ulong(2_000_000).fixed.sum = 142913828922);
141 
142     auto ps = Sieve!int();
143     assert(ps.front == 2);
144     ps.popFront();
145     assert(ps.front == 3);
146 }