Skip to main content

rand_unique/
builder.rs

1use num_traits::{AsPrimitive, PrimInt, WrappingAdd, WrappingSub};
2
3use crate::sequence::RandomSequence;
4
5/// The configuration for [RandomSequence], a random unique sequence generator.
6///
7/// These variables define the entire sequence and should not be modified with the exception of
8/// `seed_base` and `seed_offset` during initialisation.
9///
10/// The builder defines the internal properties of the sequence, and serialization includes all
11/// of the properties to preserve the sequence between crate versions which may change the fixed
12/// values between minor versions.
13///
14/// Crate versioning will bump:
15/// - _Minor version_: when the hard coded parameters are updated in favour of better ones. It is
16///    safe to serialize the [RandomSequenceBuilder] between minor versions.
17/// - _Major version_: when the sequence generation logic fundamentally changes the sequence,
18///    meaning it would be potentially unsafe to serialize the [RandomSequenceBuilder] between
19///    major crate version changes.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub struct RandomSequenceBuilder<T>
23where
24    T: QuadraticResidue
25{
26    /// The seed for the the start index.
27    pub seed_base: T,
28
29    /// The seed for the offsetting addition.
30    pub seed_offset: T,
31
32    /// A value used as an xor during initialisation for `start_index = f(seed_base, init_base)` to
33    /// deterministically pseudo-randomise it.
34    pub init_base: T,
35
36    /// A value used as an xor during initialisation for `offset = f(seed_offset, init_offset)` to
37    /// deterministically pseudo-randomise it.
38    pub init_offset: T,
39
40    /// Should be the largest prime number that fits in type `T` and satisfied `prime = 3 mod 4`.
41    pub prime: T,
42
43    /// A value that provides some noise from the xor to generate a pseudo-uniform distribution.
44    pub intermediate_xor: T,
45}
46
47impl<T> RandomSequenceBuilder<T>
48where
49    T: QuadraticResidue
50{
51    /// Initialise a config from stored settings. Not recommended unless you know what you're doing,
52    /// or these values have been taken from an already serialized RandomSequenceBuilder.
53    ///
54    /// Prefer [RandomSequenceBuilderInit::new] instead.
55    #[inline]
56    pub unsafe fn from_spec(
57        seed_base: T,
58        seed_offset: T,
59        init_base: T,
60        init_offset: T,
61        prime: T,
62        intermediate_xor: T,
63    ) -> Self {
64        Self {
65            seed_base,
66            seed_offset,
67            init_base,
68            init_offset,
69            prime,
70            intermediate_xor,
71        }
72    }
73
74    /// Intermediary function to compute the quadratic prime residue.
75    #[inline(always)]
76    pub(crate) fn permute_qpr(&self, x: T) -> T {
77        // The small set of integers out of range are mapped to themselves.
78        if x >= self.prime {
79            return x;
80        }
81
82        // (x * x) % prime; but done safely to avoid integer overflow on x * x
83        // let xm = MontgomeryInt::new(x, &self.prime);
84        // let residue = (xm * xm).residue();
85
86        let residue = x.residue(self.prime);
87
88        // Op: `self.prime / 2` the bit shift is used to get around rust types
89        if x <= self.prime >> 1 {
90            residue
91        } else {
92            self.prime - residue
93        }
94    }
95}
96
97impl<T> IntoIterator for RandomSequenceBuilder<T>
98where
99    T: QuadraticResidue,
100    RandomSequence<T>: Iterator<Item = T>,
101{
102    type Item = T;
103    type IntoIter = RandomSequence<T>;
104
105    /// Build a [RandomSequence] iterator from this config.
106    #[inline]
107    fn into_iter(self) -> Self::IntoIter {
108        let start_index = self.permute_qpr(self.permute_qpr(self.seed_base).wrapping_add(&self.init_base));
109        let intermediate_offset = self.permute_qpr(self.permute_qpr(self.seed_offset).wrapping_add(&self.init_offset));
110
111        RandomSequence {
112            config: self,
113            start_index,
114            current_index: T::zero(),
115            intermediate_offset,
116            ended: false,
117        }
118    }
119}
120
121impl RandomSequenceBuilder<u8> {
122    /// Initialise a [RandomSequenceBuilder] from a specific seed pair.
123    #[inline]
124    pub fn new(seed_base: u8, seed_offset: u8) -> Self {
125        Self {
126            seed_base,
127            seed_offset,
128            init_base: 167,
129            init_offset: 181,
130            prime: 251,
131            intermediate_xor: 137,
132        }
133    }
134}
135
136impl RandomSequenceBuilder<u16> {
137    /// Initialise a [RandomSequenceBuilder] from a specific seed pair.
138    #[inline]
139    pub fn new(seed_base: u16, seed_offset: u16) -> Self {
140        Self {
141            seed_base,
142            seed_offset,
143            init_base: 0x682f,
144            init_offset: 0x4679,
145            prime: 65519,
146            intermediate_xor: 0x5bf0,
147        }
148    }
149}
150
151impl RandomSequenceBuilder<u32> {
152    /// Initialise a [RandomSequenceBuilder] from a specific seed pair.
153    #[inline]
154    pub fn new(seed_base: u32, seed_offset: u32) -> Self {
155        Self {
156            seed_base,
157            seed_offset,
158            init_base: 0x682f0161,
159            init_offset: 0x46790905,
160            prime: 4294967291,
161            intermediate_xor: 0x5bf03635,
162        }
163    }
164}
165
166impl RandomSequenceBuilder<u64> {
167    /// Initialise a [RandomSequenceBuilder] from a specific seed pair.
168    #[inline]
169    pub fn new(seed_base: u64, seed_offset: u64) -> Self {
170        Self {
171            seed_base,
172            seed_offset,
173            init_base: 0x682f01615bf03635,
174            init_offset: 0x46790905682f0161,
175            prime: 18446744073709551427,
176            intermediate_xor: 0x5bf0363546790905,
177        }
178    }
179}
180
181impl RandomSequenceBuilder<usize> {
182    /// Initialise a [RandomSequenceBuilder] from a specific seed pair.
183    #[inline]
184    pub fn new(seed_base: usize, seed_offset: usize) -> Self {
185        Self {
186            seed_base,
187            seed_offset,
188            init_base: 0x682f01615bf03635,
189            init_offset: 0x46790905682f0161,
190            prime: 18446744073709551427,
191            intermediate_xor: 0x5bf0363546790905,
192        }
193    }
194}
195
196pub trait QuadraticResidue
197where
198    Self: PrimInt + AsPrimitive<usize> + WrappingAdd + WrappingSub
199{
200    /// Compute the quadratic residue of this number against a prime.
201    fn residue(self, prime: Self) -> Self;
202}
203
204macro_rules! impl_residue {
205    ($base_type:ident, $larger_type:ident) => {
206        impl QuadraticResidue for $base_type {
207            #[inline(always)]
208            fn residue(self, prime: Self) -> Self {
209                ((self as $larger_type * self as $larger_type) % prime as $larger_type) as Self
210            }
211        }
212    };
213}
214
215impl_residue!(u8, u16);
216impl_residue!(u16, u32);
217impl_residue!(u32, u64);
218impl_residue!(u64, u128);
219#[cfg(target_pointer_width = "64")]
220impl_residue!(usize, u128);
221#[cfg(target_pointer_width = "32")]
222impl_residue!(usize, u64);
223#[cfg(not(any(target_pointer_width = "64", target_pointer_width = "32")))]
224compile_error!("Unsupported pointer width.");
225
226#[cfg(test)]
227mod tests {
228    use std::collections::hash_map::Entry;
229    use std::collections::HashMap;
230    use std::string::ToString;
231
232    use super::*;
233
234    /// Check the prime satisfies `p = 3 mod 4`.
235    fn is_3_mod_4(n: u64) -> bool {
236        n % 4 == 3
237    }
238
239    fn is_send<T: Send>() {}
240    fn is_sync<T: Sync>() {}
241
242    macro_rules! test_config {
243        ($name:ident, $type:ident, $check:literal) => {
244            #[test]
245            fn $name() {
246                let config = RandomSequenceBuilder::<$type>::new(0, 0);
247                let config_orig = config.clone();
248
249                // check the configured prime number satisfies the requirements
250                // is_prime crate makes this very quick for u64
251                let is_prime_res = is_prime::is_prime(&config.prime.to_string());
252                let is_3_mod_4_res = is_3_mod_4(config.prime as u64);
253
254                let mut found_number = config.prime;
255                if !is_prime_res || !is_3_mod_4_res {
256                    if found_number % 2 == 0 {
257                        found_number -= 1;
258                    }
259
260                    // suggest a suitable prime number (slow, but only run when there's a bad prime)
261                    let mut found_next_prime = false;
262                    let mut found_next_3_mod_4 = false;
263
264                    while !found_next_prime || !found_next_3_mod_4 {
265                        found_number -= 2;
266                        found_next_prime = is_prime::is_prime(&found_number.to_string());
267                        found_next_3_mod_4 = is_3_mod_4(found_number as u64);
268                    }
269                }
270
271                assert!(is_prime_res, "{} is not prime, suggested prime: {}", config.prime, found_number);
272                assert!(is_3_mod_4_res, "{} = 3 mod 4 doesn't hold, suggested prime: {}", config.prime, found_number);
273
274                // check config can be cloned and equality tested
275                let sequence = config.into_iter();
276                assert_eq!(sequence.config, config_orig);
277
278                // check permute_qpr for uniqueness
279                const CHECK: usize = $check;
280                let mut nums = HashMap::<$type, usize>::new();
281                for i in 0..CHECK {
282                    let num = config.permute_qpr(i as $type);
283                    match nums.entry(num) {
284                        Entry::Vacant(v) => {
285                            v.insert(i);
286                        }
287                        Entry::Occupied(o) => {
288                            panic!("Duplicate number {} at index {} and {}", num, o.get(), i);
289                        }
290                    }
291                }
292                assert_eq!(nums.len(), (0..CHECK).len());
293
294                // check builder is send and sync
295                is_send::<RandomSequenceBuilder<$type>>();
296                is_sync::<RandomSequenceBuilder<$type>>();
297            }
298        };
299    }
300
301    test_config!(test_u8_config, u8, 256);
302    test_config!(test_u16_config, u16, 65536);
303    test_config!(test_u32_config, u32, 100_000);
304    test_config!(test_u64_config, u64, 100_000);
305}