tiny_rng/lib.rs
1
2/*! ## Tiny RNG, a minimal random number generator
3Warning: Not cryptographically secure.
4
5Proven mathematical methods are applied to obtain unbiased samples.
6Specifically, rejection sampling is applied to obtain samples from
7the uniform distribution on an integer range and Fisher–Yates shuffle is
8applied to obtain a random permutation from the uniform distribution
9on the set of all permutations.
10
11```
12use tiny_rng::{Rng, Rand};
13
14let mut rng = Rng::from_seed(0);
15
16// Throw a dice:
17println!("{}", rng.rand_range_u32(1, 7));
18
19// Choose a random color:
20let colors = ["red", "green", "blue"];
21println!("{}", rng.choice(&colors));
22
23// Shuffle an array:
24let mut a = [1, 2, 3, 4];
25rng.shuffle(&mut a);
26println!("{:?}", a);
27```
28*/
29
30#![cfg_attr(not(test), no_std)]
31
32#[cfg(feature = "std")]
33extern crate std;
34
35fn wrapping_next_power_of_two_u32(x: u32) -> u32 {
36 const H: u32 = 1 << 31;
37 if x <= H {x.next_power_of_two()} else {0}
38}
39
40fn wrapping_next_power_of_two_u64(x: u64) -> u64 {
41 const H: u64 = 1 << 63;
42 if x <= H {x.next_power_of_two()} else {0}
43}
44
45/** Provided utilities.
46
47This interface permits to hide the used engine:
48```
49# use tiny_rng::{Rng, Rand};
50fn rng_from_seed(seed: u64) -> impl Rand {
51 Rng::from_seed(seed)
52}
53```
54*/
55pub trait Rand: Sized {
56 /// To obtain reproducible results.
57 /// If feature `std` is enabled, `from_time()` may be used instead,
58 /// to use system time as microseconds as a seed. Apply
59 /// `from_seed(random_seed())` to establish a faster local RNG
60 /// from a global seed RNG.
61 fn from_seed(seed: u64) -> Self;
62
63 #[cfg(feature = "std")]
64 /// Use system time as micro seconds as the seed.
65 /// Needs feature `std` to be enabled.
66 fn from_time() -> Self {
67 use std::time::SystemTime;
68 let time_seed = SystemTime::now()
69 .duration_since(SystemTime::UNIX_EPOCH)
70 .unwrap_or_else(|e| e.duration()).as_micros() as u64;
71 Self::from_seed(time_seed)
72 }
73
74 /// A sample from the uniform distribution on `0..=u32::MAX`.
75 fn rand_u32(&mut self) -> u32;
76
77 /// A sample from the uniform distribution on `0..=u8::MAX`.
78 #[inline]
79 fn rand_u8(&mut self) -> u8 {
80 self.rand_u32() as u8
81 }
82
83 /// A sample from the uniform distribution on `0..=u16::MAX`.
84 #[inline]
85 fn rand_u16(&mut self) -> u16 {
86 self.rand_u32() as u16
87 }
88
89 /// A sample from the uniform distribution on `0..=u64::MAX`.
90 #[inline]
91 fn rand_u64(&mut self) -> u64 {
92 (self.rand_u32() as u64) << 32 | (self.rand_u32() as u64)
93 }
94
95 /// A sample from the uniform distribution on `0..=usize::MAX`.
96 #[cfg(target_pointer_width = "32")]
97 #[inline]
98 fn rand_usize(&mut self) -> usize {
99 self.rand_u32() as usize
100 }
101
102 /// A sample from the uniform distribution on `0..=usize::MAX`.
103 #[cfg(target_pointer_width = "64")]
104 #[inline]
105 fn rand_usize(&mut self) -> usize {
106 self.rand_u64() as usize
107 }
108
109 /// A sample from the uniform distribution on `0..m`.
110 // Applies the idea of rejection sampling.
111 fn rand_bounded_u32(&mut self, m: u32) -> u32 {
112 let mask = wrapping_next_power_of_two_u32(m).wrapping_sub(1);
113 loop {
114 let x = mask & self.rand_u32();
115 if x < m {return x;}
116 }
117 }
118
119 /// A sample from the uniform distribution on `0..m`.
120 fn rand_bounded_u64(&mut self, m: u64) -> u64 {
121 let mask = wrapping_next_power_of_two_u64(m).wrapping_sub(1);
122 loop {
123 let x = mask & self.rand_u64();
124 if x < m {return x;}
125 }
126 }
127
128 /// A sample from the uniform distribution on `0..m`.
129 #[cfg(target_pointer_width = "32")]
130 fn rand_bounded_usize(&mut self, m: usize) -> usize {
131 self.rand_bounded_u32(m as u32) as usize
132 }
133
134 /// A sample from the uniform distribution on `0..m`.
135 #[cfg(target_pointer_width = "64")]
136 fn rand_bounded_usize(&mut self, m: usize) -> usize {
137 self.rand_bounded_u64(m as u64) as usize
138 }
139
140 /// A sample from the uniform distribution on `a..b`.
141 fn rand_range_u32(&mut self, a: u32, b: u32) -> u32 {
142 a + self.rand_bounded_u32(b - a)
143 }
144
145 /// A sample from the uniform distribution on `a..b`.
146 fn rand_range_u64(&mut self, a: u64, b: u64) -> u64 {
147 a + self.rand_bounded_u64(b - a)
148 }
149
150 /// A sample from the uniform distribution on `a..b`.
151 fn rand_range_i32(&mut self, a: i32, b: i32) -> i32 {
152 a + self.rand_bounded_u32((b - a) as u32) as i32
153 }
154
155 /// A sample from the uniform distribution on `a..b`.
156 fn rand_range_i64(&mut self, a: i64, b: i64) -> i64 {
157 a + self.rand_bounded_u64((b - a) as u64) as i64
158 }
159
160 /// A sample from the uniform distribution on the interval [0, 1).<br>
161 /// ⚠️ WARNING: Achieving uniform distribution of floats is complex and
162 /// requires analysis yet to be accomplished.
163 fn rand_f32(&mut self) -> f32 {
164 self.rand_u32() as f32 * 2.3283064E-10
165 }
166
167 /// A sample from the uniform distribution on the interval [0, 1).<br>
168 /// ⚠️ WARNING: Achieving uniform distribution of floats is complex and
169 /// requires analysis yet to be accomplished.
170 fn rand_f64(&mut self) -> f64 {
171 self.rand_u32() as f64 * 2.3283064365386963E-10
172 }
173
174 #[cfg(feature = "std")]
175 /// A sample from the normal distribution with mean `mu` and
176 /// standard deviation `sigma`.<br>
177 /// ⚠️ WARNING: depends on rand_f64, which requires further analysis.
178 // Marsaglia polar method.
179 fn rand_normal_f64(&mut self, mu: f64, sigma: f64) -> f64 {
180 loop {
181 let u = 2.0*self.rand_f64() - 1.0;
182 let v = 2.0*self.rand_f64() - 1.0;
183 let s = u*u + v*v;
184 if s < 1.0 && s > 0.0 {
185 return mu + sigma*u*f64::sqrt(-2.0*f64::ln(s)/s);
186 }
187 }
188 }
189
190 /// A sample from the uniform distribution on the non-empty slice.
191 fn choice<'a, T>(&mut self, a: &'a [T]) -> &'a T {
192 &a[self.rand_bounded_usize(a.len())]
193 }
194
195 /// Shuffle an array randomly. The method is called Fisher–Yates shuffle and has linear time complexity.
196 fn shuffle<T>(&mut self, a: &mut [T]) {
197 if a.is_empty() {return;}
198 let mut i = a.len() - 1;
199 while i > 0 {
200 let j = self.rand_bounded_usize(i + 1);
201 a.swap(i, j);
202 i -= 1;
203 }
204 }
205
206 /// Fill a buffer with random bytes.
207 fn fill(&mut self, a: &mut[u8]) {
208 let mut x = self.rand_u32();
209 let mut count = 3;
210 for p in a {
211 *p = x as u8;
212 if count == 0 {
213 x = self.rand_u32();
214 count = 3;
215 } else {
216 x >>= 8;
217 count -= 1;
218 }
219 }
220 }
221}
222
223/** A helper function to turn random number generation into an iterator.
224
225Example:
226```
227use tiny_rng::{Rng, Rand, rand_iter};
228
229let mut rng = Rng::from_seed(0);
230for x in rand_iter(&mut rng, Rand::rand_u32).take(10) {
231 println!("0x{:08x}", x);
232}
233```
234*/
235pub fn rand_iter<T: 'static, Generator: Rand>(
236 rng: &mut Generator,
237 rand: fn(&mut Generator) -> T
238) -> impl '_ + Iterator<Item = T>
239{
240 core::iter::from_fn(move || Some(rand(rng)))
241}
242
243/*
244Xorshift128+ is a modern algorithm for the fast generation of random
245numbers of relatively high quality, which passes the most important
246statistical tests. The generator is said to have a period length of
2472^128-1 and to pass "BigCrush" from the test suite "TestU01". It should
248be noted that this is by no means a cryptographic generator. The
249following implementation is as described in Vigna's article [1], with
250the assignment a=23, b=17, c=26 described there as favorable, and is
251also listed that way in the Firefox source code [2] and in
252Wikipedia [3][4]. The internal state may be arbitrary,
253but not (0, 0).
254
255Since the low-order 32 bits of a random value of Xorshift128+ are not
256supposed to pass some statistical tests, it is better to extract
257the high-order 32 bits for rand_u32.
258
259[1] Sebastiano Vigna: "Further scramblings of Marsaglia's
260xorshift generators".
261Journal of Computational and Applied Mathematics (Mai 2017).
262arXiv:1404.0390. doi:10.1016/j.cam.2016.11.006.
263https://arxiv.org/abs/1404.0390
264
265[2] mfbt/XorShift128PlusRNG.h, retrieved 8 Feb. 2020.
266https://github.com/mozilla/gecko-dev/blob/master/mfbt/XorShift128PlusRNG.h
267-- Gecko is the current engine of Firefox.
268
269[3] Xorshift. English Wikipedia, retrieved 8 Feb. 2020.
270https://en.wikipedia.org/wiki/Xorshift
271
272[4] Xorshift. German Wikipedia, retrievend 8 Feb. 2020.
273https://de.wikipedia.org/wiki/Xorshift.
274*/
275
276/// Recommended engine. Currently Xorshift128+.
277pub struct Rng {
278 state: (u64, u64)
279}
280
281impl Rand for Rng {
282 fn from_seed(seed: u64) -> Self {
283 Self {state: (
284 seed ^ 0xf4dbdf2183dcefb7, // [crc32(b"0"), crc32(b"1")]
285 seed ^ 0x1ad5be0d6dd28e9b // [crc32(b"2"), crc32(b"3")]
286 )}
287 }
288
289 fn rand_u64(&mut self) -> u64 {
290 let (mut x, y) = self.state;
291 self.state.0 = y;
292 x ^= x << 23;
293 self.state.1 = x ^ y ^ (x >> 17) ^ (y >> 26);
294 self.state.1.wrapping_add(y)
295 }
296
297 #[inline]
298 fn rand_u32(&mut self) -> u32 {
299 (self.rand_u64() >> 32) as u32
300 }
301}
302
303impl Rng {
304 /** A helper function to turn random number generation into an iterator.
305
306 Example:
307 ```
308 use tiny_rng::{Rng, Rand};
309
310 let mut rng = Rng::from_seed(0);
311 for x in rng.iter(Rand::rand_u32).take(10) {
312 println!("0x{:08x}", x);
313 }
314 ```
315 */
316 pub fn iter<T: 'static>(&mut self, rand: fn(&mut Self) -> T)
317 -> impl '_ + Iterator<Item = T>
318 {
319 rand_iter(self, rand)
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use crate::{Rng, Rand};
326
327 fn rng_test<Generator: Rand>() {
328 let mut rng = Generator::from_seed(0);
329 println!("{:?}", rng.rand_range_u32(1, 6));
330 }
331
332 #[test]
333 fn test0() {
334 rng_test::<Rng>();
335 }
336}