1 /** Optimized prime modulo calculations.
2  *
3  * Used for fast prime modulo calculations when using simple hash-functions to
4  * index in hash tables (associative arrays).
5  *
6  * See_Also: https://www.reddit.com/r/cpp/comments/anbmol/robin_hoodunordered_map_is_now_the_fastest_hashmap/
7  * See_Also: https://github.com/martinus/robin-hood-hashing
8  * See_Also: https://probablydance.com/2017/02/26/i-wrote-the-fastest-hashtable/
9  */
10 module nxt.prime_modulo;
11 
12 @safe pure nothrow @nogc:
13 
14 static assert(size_t.sizeof == 8, "This module currently only supports 64-bit platforms");
15 
16 /** Type-safe index into prime number constants `primeConstants`.
17  */
18 struct PrimeIndex
19 {
20     private ubyte _ix;          ///< The index.
21     alias _ix this;             ///< PrimeIndex becomes type-safe wrapper to `_ix`.
22 }
23 
24 /** Returns: first prime in `primeConstants` >= `value`, where the linear search
25  * in `primeConstants` starts at `primeIndex`.
26  *
27  * Increases `primeIndex` so that `primeConstants[primeIndex]` equals returned
28  * value.
29  */
30 size_t ceilingPrime(in size_t value,
31                     scope ref PrimeIndex primeIndex)
32 {
33     foreach (const nextPrimeIndex; primeIndex .. PrimeIndex(primeConstants.length))
34     {
35         immutable prime = primeConstants[nextPrimeIndex];
36         if (value <= prime)
37         {
38             primeIndex = nextPrimeIndex;
39             return prime;
40         }
41     }
42     assert(0, "Parameter value is too large");
43 }
44 
45 /// verify for small modulos
46 unittest
47 {
48     size_t value = 0;           // to be adjusted to nearest prime in `primeConstants`
49     auto i = PrimeIndex(0);
50 
51     value = 0;
52     value = ceilingPrime(value, i);
53     assert(primeConstants[i] == 0);
54 
55     value = 1;
56     value = ceilingPrime(value, i);
57     assert(primeConstants[i] == 2);
58     assert(value == 2);
59 
60     value = 2;
61     value = ceilingPrime(value, i);
62     assert(primeConstants[i] == 2);
63     assert(value == 2);
64 
65     value = 3;
66     value = ceilingPrime(value, i);
67     assert(primeConstants[i] == 3);
68     assert(value == 3);
69 
70     value = 4;
71     value = ceilingPrime(value, i);
72     assert(primeConstants[i] == 5);
73     assert(value == 5);
74 
75     value = 5;
76     value = ceilingPrime(value, i);
77     assert(primeConstants[i] == 5);
78     assert(value == 5);
79 
80     value = 6;
81     value = ceilingPrime(value, i);
82     assert(primeConstants[i] == 7);
83     assert(value == 7);
84 
85     value = 7;
86     value = ceilingPrime(value, i);
87     assert(primeConstants[i] == 7);
88 
89     foreach (const ix; 8 .. 11 + 1)
90     {
91         value = ix;
92         value = ceilingPrime(value, i);
93         assert(value == 11);
94         assert(primeConstants[i] == 11);
95     }
96 
97     foreach (const ix; 12 .. 13 + 1)
98     {
99         value = ix;
100         value = ceilingPrime(value, i);
101         assert(value == 13);
102         assert(primeConstants[i] == 13);
103     }
104 
105     foreach (const ix; 14 .. 17 + 1)
106     {
107         value = ix;
108         value = ceilingPrime(value, i);
109         assert(value == 17);
110         assert(primeConstants[i] == 17);
111     }
112 
113     foreach (const ix; 18 .. 23 + 1)
114     {
115         value = ix;
116         value = ceilingPrime(value, i);
117         assert(value == 23);
118         assert(primeConstants[i] == 23);
119     }
120 }
121 
122 /// remaining modulos
123 unittest
124 {
125     foreach (const prime; primeConstants[3 .. $])
126     {
127         size_t value = prime - 1;
128         PrimeIndex primeIndex;
129         value = ceilingPrime(value, primeIndex);
130         assert(value == prime);
131         assert(moduloPrimeIndex(value, primeIndex) == 0);
132     }
133 }
134 
135 size_t moduloPrimeIndex(in size_t value,
136                         in PrimeIndex primeIndex)
137 {
138     final switch (primeIndex)
139     {
140         static foreach (const index, const primeConstant; primeConstants)
141         {
142         case index:
143             return value % primeConstants[index];
144         }
145     }
146 }
147 
148 ///
149 unittest
150 {
151     static assert(primeConstants.length == 187);
152     assert(moduloPrimeIndex(8, PrimeIndex(3)) == 3); // modulo 5
153     assert(moduloPrimeIndex(9, PrimeIndex(4)) == 2); // modulo 7
154 }
155 
156 /// verify `moduloPrimeIndex`
157 unittest
158 {
159     static assert(primeConstants.length <= PrimeIndex._ix.max);
160     foreach (const primeIndex, const prime; primeConstants)
161     {
162         if (prime != 0)
163         {
164             assert(moduloPrimeIndex(prime + 0, PrimeIndex(cast(typeof(PrimeIndex._ix))primeIndex)) == 0);
165             assert(moduloPrimeIndex(prime + 1, PrimeIndex(cast(typeof(PrimeIndex._ix))primeIndex)) == 1);
166         }
167     }
168 }
169 
170 private static:
171 
172 /** Subset of prime constants in the exclusive range `[0 .. size_t.max]`.
173  *
174  * Suitable for use as lengths of a growing hash table.
175  */
176 static immutable size_t[] primeConstants =
177 [
178     0UL, 2UL, 3UL, 5UL, 7UL, 11UL, 13UL, 17UL, 23UL, 29UL, 37UL, 47UL,
179     59UL, 73UL, 97UL, 127UL, 151UL, 197UL, 251UL, 313UL, 397UL,
180     499UL, 631UL, 797UL, 1009UL, 1259UL, 1597UL, 2011UL, 2539UL,
181     3203UL, 4027UL, 5087UL, 6421UL, 8089UL, 10193UL, 12853UL, 16193UL,
182     20399UL, 25717UL, 32401UL, 40823UL, 51437UL, 64811UL, 81649UL,
183     102877UL, 129607UL, 163307UL, 205759UL, 259229UL, 326617UL,
184     411527UL, 518509UL, 653267UL, 823117UL, 1037059UL, 1306601UL,
185     1646237UL, 2074129UL, 2613229UL, 3292489UL, 4148279UL, 5226491UL,
186     6584983UL, 8296553UL, 10453007UL, 13169977UL, 16593127UL, 20906033UL,
187     26339969UL, 33186281UL, 41812097UL, 52679969UL, 66372617UL,
188     83624237UL, 105359939UL, 132745199UL, 167248483UL, 210719881UL,
189     265490441UL, 334496971UL, 421439783UL, 530980861UL, 668993977UL,
190     842879579UL, 1061961721UL, 1337987929UL, 1685759167UL, 2123923447UL,
191     2675975881UL, 3371518343UL, 4247846927UL, 5351951779UL, 6743036717UL,
192     8495693897UL, 10703903591UL, 13486073473UL, 16991387857UL,
193     21407807219UL, 26972146961UL, 33982775741UL, 42815614441UL,
194     53944293929UL, 67965551447UL, 85631228929UL, 107888587883UL,
195     135931102921UL, 171262457903UL, 215777175787UL, 271862205833UL,
196     342524915839UL, 431554351609UL, 543724411781UL, 685049831731UL,
197     863108703229UL, 1087448823553UL, 1370099663459UL, 1726217406467UL,
198     2174897647073UL, 2740199326961UL, 3452434812973UL, 4349795294267UL,
199     5480398654009UL, 6904869625999UL, 8699590588571UL, 10960797308051UL,
200     13809739252051UL, 17399181177241UL, 21921594616111UL, 27619478504183UL,
201     34798362354533UL, 43843189232363UL, 55238957008387UL, 69596724709081UL,
202     87686378464759UL, 110477914016779UL, 139193449418173UL,
203     175372756929481UL, 220955828033581UL, 278386898836457UL,
204     350745513859007UL, 441911656067171UL, 556773797672909UL,
205     701491027718027UL, 883823312134381UL, 1113547595345903UL,
206     1402982055436147UL, 1767646624268779UL, 2227095190691797UL,
207     2805964110872297UL, 3535293248537579UL, 4454190381383713UL,
208     5611928221744609UL, 7070586497075177UL, 8908380762767489UL,
209     11223856443489329UL, 14141172994150357UL, 17816761525534927UL,
210     22447712886978529UL, 28282345988300791UL, 35633523051069991UL,
211     44895425773957261UL, 56564691976601587UL, 71267046102139967UL,
212     89790851547914507UL, 113129383953203213UL, 142534092204280003UL,
213     179581703095829107UL, 226258767906406483UL, 285068184408560057UL,
214     359163406191658253UL, 452517535812813007UL, 570136368817120201UL,
215     718326812383316683UL, 905035071625626043UL, 1140272737634240411UL,
216     1436653624766633509UL, 1810070143251252131UL, 2280545475268481167UL,
217     2873307249533267101UL, 3620140286502504283UL, 4561090950536962147UL,
218     5746614499066534157UL, 7240280573005008577UL, 9122181901073924329UL,
219     11493228998133068689UL, 14480561146010017169UL, 18446744073709551557UL,
220 ];
221 
222 version(none)   // deprecated by `switch` over `static foreach` in `moduloPrimeIndex`
223 {
224     static foreach (primeConstant; primeConstants)
225     {
226         static if (primeConstant == 0)
227         {
228             mixin(`size_t mod` ~ primeConstant.stringof[0 .. $-2] ~ `(const size_t) { return ` ~ primeConstant.stringof ~ `; }`);
229         }
230         else
231         {
232             mixin(`size_t mod` ~ primeConstant.stringof[0 .. $-2] ~ `(const size_t value) { return value % ` ~ primeConstant.stringof ~ `; }`);
233         }
234     }
235     static immutable moduloPrimeFns = [
236         &mod0, &mod2, &mod3, &mod5, &mod7, &mod11, &mod13, &mod17, &mod23, &mod29, &mod37,
237         &mod47, &mod59, &mod73, &mod97, &mod127, &mod151, &mod197, &mod251, &mod313, &mod397,
238         &mod499, &mod631, &mod797, &mod1009, &mod1259, &mod1597, &mod2011, &mod2539, &mod3203,
239         &mod4027, &mod5087, &mod6421, &mod8089, &mod10193, &mod12853, &mod16193, &mod20399,
240         &mod25717, &mod32401, &mod40823, &mod51437, &mod64811, &mod81649, &mod102877,
241         &mod129607, &mod163307, &mod205759, &mod259229, &mod326617, &mod411527, &mod518509,
242         &mod653267, &mod823117, &mod1037059, &mod1306601, &mod1646237, &mod2074129,
243         &mod2613229, &mod3292489, &mod4148279, &mod5226491, &mod6584983, &mod8296553,
244         &mod10453007, &mod13169977, &mod16593127, &mod20906033, &mod26339969, &mod33186281,
245         &mod41812097, &mod52679969, &mod66372617, &mod83624237, &mod105359939, &mod132745199,
246         &mod167248483, &mod210719881, &mod265490441, &mod334496971, &mod421439783,
247         &mod530980861, &mod668993977, &mod842879579, &mod1061961721, &mod1337987929,
248         &mod1685759167, &mod2123923447, &mod2675975881, &mod3371518343, &mod4247846927,
249         &mod5351951779, &mod6743036717, &mod8495693897, &mod10703903591, &mod13486073473,
250         &mod16991387857, &mod21407807219, &mod26972146961, &mod33982775741, &mod42815614441,
251         &mod53944293929, &mod67965551447, &mod85631228929, &mod107888587883, &mod135931102921,
252         &mod171262457903, &mod215777175787, &mod271862205833, &mod342524915839,
253         &mod431554351609, &mod543724411781, &mod685049831731, &mod863108703229,
254         &mod1087448823553, &mod1370099663459, &mod1726217406467, &mod2174897647073,
255         &mod2740199326961, &mod3452434812973, &mod4349795294267, &mod5480398654009,
256         &mod6904869625999, &mod8699590588571, &mod10960797308051, &mod13809739252051,
257         &mod17399181177241, &mod21921594616111, &mod27619478504183, &mod34798362354533,
258         &mod43843189232363, &mod55238957008387, &mod69596724709081, &mod87686378464759,
259         &mod110477914016779, &mod139193449418173, &mod175372756929481, &mod220955828033581,
260         &mod278386898836457, &mod350745513859007, &mod441911656067171, &mod556773797672909,
261         &mod701491027718027, &mod883823312134381, &mod1113547595345903, &mod1402982055436147,
262         &mod1767646624268779, &mod2227095190691797, &mod2805964110872297, &mod3535293248537579,
263         &mod4454190381383713, &mod5611928221744609, &mod7070586497075177, &mod8908380762767489,
264         &mod11223856443489329, &mod14141172994150357, &mod17816761525534927,
265         &mod22447712886978529, &mod28282345988300791, &mod35633523051069991,
266         &mod44895425773957261, &mod56564691976601587, &mod71267046102139967,
267         &mod89790851547914507, &mod113129383953203213, &mod142534092204280003,
268         &mod179581703095829107, &mod226258767906406483, &mod285068184408560057,
269         &mod359163406191658253, &mod452517535812813007, &mod570136368817120201,
270         &mod718326812383316683, &mod905035071625626043, &mod1140272737634240411,
271         &mod1436653624766633509, &mod1810070143251252131, &mod2280545475268481167,
272         &mod2873307249533267101, &mod3620140286502504283, &mod4561090950536962147,
273         &mod5746614499066534157, &mod7240280573005008577, &mod9122181901073924329,
274         &mod11493228998133068689, &mod14480561146010017169, &mod18446744073709551557,
275         ];
276 }