ya_rand/
lib.rs

1/*!
2# YA-Rand: Yet Another Rand
3
4Simple and fast pseudo/crypto random number generation.
5
6## Performance considerations
7
8The backing CRNG uses compile-time dispatch, so you'll only get the fastest implementation available to the
9machine if rust knows what kind of machine to compile for.
10
11Your best bet is to configure your global .cargo/config.toml with `rustflags = ["-C", "target-cpu=native"]`
12beneath the `[build]` directive.
13
14If you know the [x86 feature level] of the processor that will be executing your binaries,
15it maybe be better to instead configure this directive at the crate level.
16
17[x86 feature level]: https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels
18
19## But why?
20
21Because `rand` is very cool and extremely powerful, but kind of an enormous fucking pain in the ass
22to use, and it's far too large and involved for someone who just needs to flip a coin once
23every few minutes. But if you're doing some crazy black magic numerical sorcery, it almost certainly
24has something you can use to complete your spell. Don't be afraid to go there if you need to.
25
26Other crates, like `fastrand`, `tinyrand`, or `oorandom`, fall somewhere between "I'm not sure I trust
27the backing RNG" (state size is too small or algorithm is iffy) and "this API is literally just
28`rand` but far less powerful". I wanted something easy to use, but also fast and statistically robust.
29
30So here we are.
31
32## Usage
33
34Import the contents of the library and use [`new_rng`] to create new RNGs wherever
35you need them. Then call whatever method you require on those instances. All methods available
36are directly accessible through any generator instance via the dot operator, and are named
37in a way that should make it easy to quickly identify what you need. See below for a few examples.
38
39If you need cryptographic security, [`new_rng_secure`] will provide you with a [`SecureRng`] instance,
40suitable for use in secure contexts.
41
42"How do I access the thread-local RNG?" There isn't one, and unless Rust improves the performance and
43ergonomics of the TLS implementation, there probably won't ever be. Create a local instance when and
44where you need one and use it while you need it. If you need an RNG to stick around for a while, passing
45it between functions or storing it in structs is a perfectly valid solution.
46
47```
48use ya_rand::*;
49
50// **Correct** instantiation is very easy.
51// Seeds a PRNG instance using operating system entropy,
52// so you never have to worry about the quality of the
53// initial state.
54let mut rng = new_rng();
55
56// Generate a random number with a given upper bound.
57let max: u64 = 420;
58let val = rng.bound(max);
59assert!(val < max);
60
61// Generate a random number in a given range.
62let min: i64 = -69;
63let max: i64 = 69;
64let val = rng.range(min, max);
65assert!(min <= val && val < max);
66
67// Generate a random floating point value.
68let val = rng.f64();
69assert!(0.0 <= val && val < 1.0);
70
71// Generate a random ascii digit: '0'..='9' as a char.
72let digit = rng.ascii_digit();
73assert!(digit.is_ascii_digit());
74
75// Seeds a CRNG instance with OS entropy.
76let mut secure_rng = new_rng_secure();
77
78// We still have access to all the same methods...
79let val = rng.f64();
80assert!(0.0 <= val && val < 1.0);
81
82// ...but since the CRNG is secure, we also
83// get some nice extras.
84// Here, we generate a string of random hexidecimal
85// characters (base 16), with the shortest length guaranteed
86// to be secure.
87use ya_rand::encoding::*;
88let s = secure_rng.text::<Base16>(Base16::MIN_LEN);
89assert!(s.len() == Base16::MIN_LEN);
90```
91
92## Features
93
94* **std** -
95    Enabled by default, but can be disabled for use in `no_std` environments. Enables normal/exponential
96    distributions, error type conversions for getrandom, and the **alloc** feature.
97* **alloc** -
98    Enabled by default. Normally enabled through **std**, but can be enabled on it's own for use in
99    `no_std` environments which provide allocation primitives. Enables random generation of secure
100    `String` values when using [`SecureRng`].
101* **secure** -
102    Enabled by default. Provides [`SecureRng`], which implements [SecureGenerator]. The backing generator
103    is ChaCha with 8 rounds and a 64-bit counter.
104* **inline** -
105    Marks all [`Generator::u64`] implementations with `#[inline]`. Should generally increase
106    runtime performance at the cost of binary size and compile time.
107    You'll have to test your specific use case to determine if this feature is worth it for you;
108    all the RNGs provided tend to be plenty fast without additional inlining.
109
110## Details
111
112This crate primarily uses the [xoshiro] family for pseudo-random number generators. These generators are
113very fast, of [very high statistical quality], and small. They aren't cryptographically secure,
114but most users don't need their RNG to be secure, they just need it to be random and fast. The default
115generator is xoshiro256++, which should provide a large enough period for most users. The xoshiro512++
116generator is also provided in case you need a longer period.
117
118[xoshiro]: https://prng.di.unimi.it/
119[very high statistical quality]: https://vigna.di.unimi.it/ftp/papers/ScrambledLinear.pdf
120
121Since version 2.0, [`RomuTrio`] and [`RomuQuad`] from the [romurand] family are also provided. These are
122non-linear generators which can be ever-so-slightly faster than the xoshiro generators, particularly when
123the `inline` feature is enabled. But in practice this difference likely won't be measurable. Unless you're
124especially fond of the statistical properties of the romurand generators, this crates default generator
125should be more than enough.
126
127[romurand]: https://romu-random.org/
128
129All generators output a distinct `u64` value on each call, and the various methods used for transforming
130those outputs into more usable forms are all high-quality and well-understood. Placing an upper bound
131on these values uses [Lemire's method]. Both inclusive bounding and range-based bounding are applications
132of this method, with a few intermediary steps to adjust the bound and apply shifts as needed.
133This approach is unbiased and quite fast, but for very large bounds performance might degrade slightly,
134since the algorithm may need to sample the underlying RNG multiple times to get an unbiased result.
135But this is just a byproduct of how the underlying algorithm works, and isn't something you should ever be
136worried about when using the aforementioned methods, since these resamples are few and far between.
137If your bound happens to be a power of 2, always use [`Generator::bits`], since it's nothing more
138than a bit-shift of the original `u64` provided by the RNG, and will always be as fast as possible.
139
140Floating point values (besides the normal and exponential distributions) are uniformly distributed,
141with all the possible outputs being equidistant within the given interval. They are **not** maximally dense;
142if that's something you need, you'll have to generate those values yourself. This approach is very fast, and
143endorsed by both [Lemire] and [Vigna] (the author of the RNGs used in this crate). The normal distribution
144implementation uses the [Marsaglia polar method], returning pairs of independently sampled `f64` values.
145Exponential variates are generated using [this approach].
146
147[Lemire's method]: https://arxiv.org/abs/1805.10941
148[Lemire]: https://lemire.me/blog/2017/02/28/how-many-floating-point-numbers-are-in-the-interval-01/
149[Vigna]: https://prng.di.unimi.it/#remarks
150[Marsaglia polar method]: https://en.wikipedia.org/wiki/Marsaglia_polar_method
151[this approach]: https://en.wikipedia.org/wiki/Exponential_distribution#Random_variate_generation
152
153## Security
154
155If you're in the market for secure random number generation, this crate provides a secure generator backed
156by a highly optimized ChaCha8 implementation from the [`chachacha`] crate.
157It functions identically to the other provided RNGs, but with added functionality that wouldn't be safe to
158use on pseudo RNGs. Why only 8 rounds? Because people who are very passionate about cryptography are convinced
159that's enough, and I have zero reason to doubt them, nor any capacity to prove them wrong.
160See page 14 of the [`Too Much Crypto`] paper if you're interested in the justification.
161
162The security guarantees made to the user are identical to those made by ChaCha as an algorithm. It is up
163to you to determine if those guarantees meet the demands of your use case.
164
165I reserve the right to change the backing implementation at any time to another RNG which is at least as secure,
166without changing the API or bumping the major/minor version. Realistically, this just means I'm willing to bump
167this to ChaCha12 if ChaCha8 is ever compromised.
168
169[`Too Much Crypto`]: https://eprint.iacr.org/2019/1492
170
171## Safety
172
173Generators are seeded using entropy from the underlying OS, and have the potential to fail during creation.
174But in practice this is extraordinarily unlikely, and isn't something the end-user should ever worry about.
175Modern Windows versions (10 and newer) have a crypto subsystem that will never fail during runtime, and
176rust can trivially remove the failure branch when compiling binaries for those systems.
177*/
178
179#![allow(clippy::doc_overindented_list_items)]
180#![deny(missing_docs)]
181#![no_std]
182
183#[cfg(all(feature = "alloc", feature = "secure"))]
184extern crate alloc;
185
186#[cfg(all(feature = "alloc", feature = "secure"))]
187pub mod encoding;
188mod rng;
189mod romuquad;
190mod romutrio;
191#[cfg(feature = "secure")]
192mod secure;
193mod util;
194mod xoshiro256pp;
195mod xoshiro512pp;
196
197#[cfg(feature = "secure")]
198pub use rng::SecureGenerator;
199pub use rng::{Generator, SeedableGenerator};
200pub use romuquad::RomuQuad;
201pub use romutrio::RomuTrio;
202#[cfg(feature = "secure")]
203pub use secure::SecureRng;
204pub use xoshiro256pp::Xoshiro256pp;
205pub use xoshiro512pp::Xoshiro512pp;
206
207/// The recommended generator for all non-cryptographic purposes.
208pub type ShiroRng = Xoshiro256pp;
209
210/// The recommended way to create new PRNG instances.
211///
212/// Identical to calling [`ShiroRng::new`] or [`Xoshiro256pp::new`].
213#[inline]
214pub fn new_rng() -> ShiroRng {
215    ShiroRng::new()
216}
217
218/// The recommended way to create new CRNG instances.
219///
220/// Identical to calling [`SecureRng::new`].
221#[cfg(feature = "secure")]
222#[inline]
223pub fn new_rng_secure() -> SecureRng {
224    SecureRng::new()
225}
226
227#[cfg(test)]
228mod tests {
229    use super::encoding::*;
230    use super::*;
231    use alloc::collections::BTreeSet;
232
233    const ITERATIONS: usize = 12357;
234    const ITERATIONS_LONG: usize = 1 << 24;
235
236    #[test]
237    pub fn ascii_alphabetic() {
238        let mut rng = new_rng();
239        let mut vals = BTreeSet::new();
240        for _ in 0..ITERATIONS {
241            let result = rng.ascii_alphabetic();
242            assert!(result.is_ascii_alphabetic());
243            vals.insert(result);
244        }
245        assert!(vals.len() == 52);
246    }
247
248    #[test]
249    pub fn ascii_uppercase() {
250        let mut rng = new_rng();
251        let mut vals = BTreeSet::new();
252        for _ in 0..ITERATIONS {
253            let result = rng.ascii_uppercase();
254            assert!(result.is_ascii_uppercase());
255            vals.insert(result);
256        }
257        assert!(vals.len() == 26);
258    }
259
260    #[test]
261    pub fn ascii_lowercase() {
262        let mut rng = new_rng();
263        let mut vals = BTreeSet::new();
264        for _ in 0..ITERATIONS {
265            let result = rng.ascii_lowercase();
266            assert!(result.is_ascii_lowercase());
267            vals.insert(result);
268        }
269        assert!(vals.len() == 26);
270    }
271
272    #[test]
273    pub fn ascii_alphanumeric() {
274        let mut rng = new_rng();
275        let mut vals = BTreeSet::new();
276        for _ in 0..ITERATIONS {
277            let result = rng.ascii_alphanumeric();
278            assert!(result.is_ascii_alphanumeric());
279            vals.insert(result);
280        }
281        assert!(vals.len() == 62);
282    }
283
284    #[test]
285    pub fn ascii_digit() {
286        let mut rng = new_rng();
287        let mut vals = BTreeSet::new();
288        for _ in 0..ITERATIONS {
289            let result = rng.ascii_digit();
290            assert!(result.is_ascii_digit());
291            vals.insert(result);
292        }
293        assert!(vals.len() == 10);
294    }
295
296    #[test]
297    fn text_base64() {
298        test_text::<Base64>();
299    }
300
301    #[test]
302    fn text_base64_url() {
303        test_text::<Base64Url>();
304    }
305
306    #[test]
307    fn text_base62() {
308        test_text::<Base62>();
309    }
310
311    #[test]
312    fn text_base32() {
313        test_text::<Base32>();
314    }
315
316    #[test]
317    fn text_base32_hex() {
318        test_text::<Base32Hex>();
319    }
320
321    #[test]
322    fn text_base16() {
323        test_text::<Base16>();
324    }
325
326    fn test_text<E: Encoder>() {
327        let s = new_rng_secure().text::<E>(ITERATIONS);
328        let distinct_bytes = s.bytes().collect::<BTreeSet<_>>();
329        let distinct_chars = s.chars().collect::<BTreeSet<_>>();
330
331        let lengths_are_equal = ITERATIONS == s.len()
332            && E::CHARSET.len() == distinct_bytes.len()
333            && E::CHARSET.len() == distinct_chars.len();
334        assert!(lengths_are_equal);
335
336        let contains_all_values = E::CHARSET.iter().all(|c| distinct_bytes.contains(c));
337        assert!(contains_all_values);
338
339        // Pretty sure this isn't necessary because of the above length
340        // checks but extra testing is fine by me.
341        let all_values_are_ascii = distinct_chars.iter().all(|c| c.is_ascii());
342        assert!(all_values_are_ascii);
343    }
344
345    #[test]
346    fn wide_mul() {
347        const SHIFT: u32 = 48;
348        const EXPECTED_HIGH: u64 = 1 << ((SHIFT * 2) - u64::BITS);
349        const EXPECTED_LOW: u64 = 0;
350        let x = 1 << SHIFT;
351        let y = x;
352        // 2^48 * 2^48 = 2^96
353        let (high, low) = util::wide_mul(x, y);
354        assert!(high == EXPECTED_HIGH);
355        assert!(low == EXPECTED_LOW);
356    }
357
358    #[test]
359    fn f64() {
360        let mut rng = new_rng();
361        for _ in 0..ITERATIONS_LONG {
362            let val = rng.f64();
363            assert!((0.0..1.0).contains(&val));
364        }
365    }
366
367    #[test]
368    fn f32() {
369        let mut rng = new_rng();
370        for _ in 0..ITERATIONS_LONG {
371            let val = rng.f32();
372            assert!((0.0..1.0).contains(&val));
373        }
374    }
375
376    #[test]
377    fn f64_nonzero() {
378        let mut rng = new_rng();
379        for _ in 0..ITERATIONS_LONG {
380            let val = rng.f64_nonzero();
381            assert!(0.0 < val && val <= 1.0);
382        }
383    }
384
385    #[test]
386    fn f32_nonzero() {
387        let mut rng = new_rng();
388        for _ in 0..ITERATIONS_LONG {
389            let val = rng.f32_nonzero();
390            assert!(0.0 < val && val <= 1.0);
391        }
392    }
393
394    #[test]
395    fn f64_wide() {
396        let mut rng = new_rng();
397        for _ in 0..ITERATIONS_LONG {
398            let val = rng.f64_wide();
399            assert!(val.abs() < 1.0);
400        }
401    }
402
403    #[test]
404    fn f32_wide() {
405        let mut rng = new_rng();
406        for _ in 0..ITERATIONS_LONG {
407            let val = rng.f32_wide();
408            assert!(val.abs() < 1.0);
409        }
410    }
411}