unirand/lib.rs
1//! # Unirand Crate
2//!
3//! This crate implements Marsaglia's Universal Random Number Generator.
4//! [More details on the original paper](https://www.sciencedirect.com/science/article/abs/pii/016771529090092L).
5//!
6//! ## Overview
7//!
8//! The RNG uses a sequence of operations to generate uniformly distributed
9//! random numbers between 0 and 1. It has been designed with simplicity and
10//! reproducibility in mind.
11//!
12//! ## Usage Instructions
13//!
14//! To use this crate, add the following dependency to your `Cargo.toml`:
15//!
16//! ```toml
17//! [dependencies]
18//! unirand = "0.1.2"
19//! ```
20//!
21//! Then, you can initialise and use the RNG in your project as follows:
22//!
23//! ```rust
24//! use unirand::MarsagliaUniRng;
25
26//! let mut rng = MarsagliaUniRng::new();
27//! rng.rinit(170);
28//! println!("Random number: {}", rng.uni());
29//! ```
30//!
31//! ## Further Information
32//!
33//! See the documentation for individual functions and methods below for more details.
34
35const LEN_U: usize = 98; // Length of the random values array.
36
37/// A struct representing Marsaglia's Universal Random Number Generator.
38pub struct MarsagliaUniRng {
39 uni_u: [f32; LEN_U], // Array holding the recent random numbers.
40 uni_c: f32, // Correction to avoid periodicity.
41 uni_cd: f32, // Correction delta value.
42 uni_cm: f32, // Correction modulus.
43 uni_ui: usize, // Current position in the random values array.
44 uni_uj: usize, // Second index used for generating new numbers.
45}
46
47impl Default for MarsagliaUniRng {
48 fn default() -> Self {
49 Self::new()
50 }
51}
52
53impl MarsagliaUniRng {
54 /// Creates a new instance of the RNG.
55 ///
56 /// # Example
57 ///
58 /// ```rust
59 /// use unirand::MarsagliaUniRng;
60 ///
61 /// let rng = MarsagliaUniRng::new();
62 /// ```
63 pub fn new() -> Self {
64 Self {
65 uni_u: [0.0; LEN_U],
66 uni_c: 0.0,
67 uni_cd: 0.0,
68 uni_cm: 0.0,
69 uni_ui: 0,
70 uni_uj: 0,
71 }
72 }
73
74 /// Generates a new random float value between 0 and 1.
75 ///
76 /// # Example
77 ///
78 /// ```rust
79 /// use unirand::MarsagliaUniRng;
80 ///
81 /// let mut rng = MarsagliaUniRng::new();
82 /// rng.rinit(170);
83 /// let number = rng.uni();
84 /// println!("Random number: {}", number);
85 /// ```
86 pub fn uni(&mut self) -> f32 {
87 let mut luni = self.uni_u[self.uni_ui] - self.uni_u[self.uni_uj];
88 if luni < 0.0 {
89 luni += 1.0;
90 }
91 self.uni_u[self.uni_ui] = luni;
92
93 // Adjust indices for the next random number generation.
94 if self.uni_ui == 0 {
95 self.uni_ui = 97;
96 } else {
97 self.uni_ui -= 1;
98 }
99 if self.uni_uj == 0 {
100 self.uni_uj = 97;
101 } else {
102 self.uni_uj -= 1;
103 }
104
105 self.uni_c -= self.uni_cd;
106 if self.uni_c < 0.0 {
107 self.uni_c += self.uni_cm;
108 }
109
110 luni -= self.uni_c;
111 if luni < 0.0 {
112 luni += 1.0;
113 }
114 luni
115 }
116
117 /// Initialises the random values array using four seeds.
118 ///
119 /// # Parameters
120 ///
121 /// - `i`, `j`, `k`, `l`: The seed values used for initialisation.
122 pub fn rstart(&mut self, mut i: i32, mut j: i32, mut k: i32, mut l: i32) {
123 for ii in 1..=97 {
124 let mut s = 0.0;
125 let mut t = 0.5;
126 for _ in 1..=24 {
127 let m = ((i * j % 179) * k) % 179;
128 i = j;
129 j = k;
130 k = m;
131 l = (53 * l + 1) % 169;
132 if l * m % 64 >= 32 {
133 s += t;
134 }
135 t *= 0.5;
136 }
137 self.uni_u[ii] = s;
138 }
139 // Set fixed correction values.
140 self.uni_c = 362436.0 / 16777216.0;
141 self.uni_cd = 7654321.0 / 16777216.0;
142 self.uni_cm = 16777213.0 / 16777216.0;
143 self.uni_ui = 97;
144 self.uni_uj = 33;
145 }
146
147 /// Validates and decomposes a single seed into four seeds, then initialises the random values array.
148 ///
149 /// # Panics
150 ///
151 /// Panics if the seed (`ijkl`) is out of range or if the generated seeds are invalid.
152 pub fn rinit(&mut self, ijkl: i32) {
153 if !(0..=900_000_000).contains(&ijkl) {
154 panic!("rinit: ijkl = {ijkl} -- out of range");
155 }
156
157 let ij = ijkl / 30082;
158 let kl = ijkl - (30082 * ij);
159 let i = ((ij / 177) % 177) + 2;
160 let j = (ij % 177) + 2;
161 let k = ((kl / 169) % 178) + 1;
162 let l = kl % 169;
163
164 if !(1..=178).contains(&i) {
165 panic!("rinit: i = {i} -- out of range");
166 }
167 if !(2..=178).contains(&j) {
168 panic!("rinit: j = {j} -- out of range");
169 }
170 if !(1..=178).contains(&k) {
171 panic!("rinit: k = {k} -- out of range");
172 }
173 if !(0..=168).contains(&l) {
174 panic!("rinit: l = {l} -- out of range");
175 }
176 if i == 1 && j == 1 && k == 1 {
177 panic!("rinit: 1 1 1 not allowed for 1st 3 seeds");
178 }
179
180 self.rstart(i, j, k, l);
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::MarsagliaUniRng;
187
188 /// This test checks that a known valid seed produces the expected output.
189 #[test]
190 fn test_rng_output() {
191 let mut rng = MarsagliaUniRng::new();
192 rng.rinit(170);
193 let random_value = rng.uni();
194 let expected = 0.68753344;
195 let tolerance = 1e-6;
196 assert!(
197 (random_value - expected).abs() < tolerance,
198 "Expected {}, got {}",
199 expected,
200 random_value
201 );
202 }
203
204 /// This test verifies that out-of-range seeds cause expected panics.
205 #[test]
206 #[should_panic(expected = "rinit: ijkl = -1 -- out of range")]
207 fn test_rinit_panics_low_seed() {
208 let mut rng = MarsagliaUniRng::new();
209 rng.rinit(-1); // Below valid range
210 }
211
212 #[test]
213 #[should_panic(expected = "rinit: ijkl = 900000001 -- out of range")]
214 fn test_rinit_panics_high_seed() {
215 let mut rng = MarsagliaUniRng::new();
216 rng.rinit(900_000_001); // Above valid range
217 }
218
219 /// This is a Statistical Quality Test (SQT) to ensure the RNG produces a uniform distribution.
220 #[test]
221 fn test_rng_statistics() {
222 let mut rng = MarsagliaUniRng::new();
223 rng.rinit(170);
224 let n = 10_000;
225 let sum: f32 = (0..n).map(|_| rng.uni()).sum();
226 let mean = sum / n as f32;
227 assert!(
228 (mean - 0.5).abs() < 0.01,
229 "Mean out of expected range: {}",
230 mean
231 );
232 }
233
234 /// This test checks for reproducibility with repeated initialisations using the same seed.
235 #[test]
236 fn test_rng_reproducibility() {
237 let mut rng1 = MarsagliaUniRng::new();
238 let mut rng2 = MarsagliaUniRng::new();
239 rng1.rinit(42);
240 rng2.rinit(42);
241 for _ in 0..100 {
242 assert!((rng1.uni() - rng2.uni()).abs() < 1e-7);
243 }
244 }
245}