spinoso_random/rand.rs
1use core::fmt;
2
3use rand::Rng;
4
5use crate::{ArgumentError, Random};
6
7/// A range constraint for generating random numbers.
8///
9/// This enum is an input to the [`rand()`] function. See its documentation for
10/// more details.
11// TODO: Add range variants
12#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
13#[cfg_attr(docsrs, doc(cfg(feature = "rand-method")))]
14pub enum Max {
15 /// A maximum float bound.
16 ///
17 /// This bound is exclusive.
18 Float(f64),
19 /// A maximum integer bound.
20 ///
21 /// This bound is exclusive.
22 Integer(i64),
23 /// The default bound when no bound is supplied.
24 ///
25 /// This bound corresponds to [`Max::Float(1.0)`](Self::Float).
26 None,
27}
28
29impl Default for Max {
30 fn default() -> Self {
31 Self::None
32 }
33}
34
35impl fmt::Display for Max {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 match self {
38 Self::Float(max) => write!(f, "{max}"),
39 Self::Integer(max) => write!(f, "{max}"),
40 Self::None => f.write_str("Infinity"),
41 }
42 }
43}
44
45/// A generated random number.
46///
47/// This enum is returned by the [`rand()`] function. See its documentation for
48/// more details.
49#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
50#[cfg_attr(docsrs, doc(cfg(feature = "rand-method")))]
51pub enum Rand {
52 /// A random float.
53 ///
54 /// A random float is returned from [`rand()`] when given [`Max::Float`] or
55 /// [`Max::None`] max constraint.
56 Float(f64),
57 /// A random integer.
58 ///
59 /// A random integer is returned from [`rand()`] when given [`Max::Integer`]
60 /// max constraint.
61 Integer(i64),
62}
63
64/// Generate random numbers bounded from below by 0 and above by the given max.
65///
66/// When `max` is an `i64`, `rand` returns a random integer greater than or
67/// equal to zero and less than `max`.
68///
69/// When `max` is an `f64`, `rand` returns a random floating point number
70/// between 0.0 and max, including 0.0 and excluding `max`.
71///
72/// # Implementation notes
73///
74/// This function does not yet support range constraints. When support is added,
75/// when `max` is a `Range`, `rand` will return a random number where
76/// `range.member?(number) == true`.
77///
78/// # Examples
79///
80/// Generate floats from `(0.0..1.0)`:
81///
82/// ```
83/// use spinoso_random::{rand, ArgumentError, InitializeError, Max, Rand, Random};
84///
85/// # #[derive(Debug)]
86/// # enum ExampleError { Argument(ArgumentError), Initialize(InitializeError) }
87/// # impl From<ArgumentError> for ExampleError { fn from(err: ArgumentError) -> Self { Self::Argument(err) } }
88/// # impl From<InitializeError> for ExampleError { fn from(err: InitializeError) -> Self { Self::Initialize(err) } }
89/// # fn example() -> Result<(), ExampleError> {
90/// let mut random = Random::new()?;
91/// let max = Max::None;
92/// let rand = rand(&mut random, max)?;
93/// assert!(matches!(rand, Rand::Float(x) if x < 1.0));
94/// # Ok(())
95/// # }
96/// # example().unwrap();
97/// ```
98///
99/// Generate random integers:
100///
101/// ```
102/// use spinoso_random::{rand, ArgumentError, InitializeError, Max, Rand, Random};
103///
104/// # #[derive(Debug)]
105/// # enum ExampleError { Argument(ArgumentError), Initialize(InitializeError) }
106/// # impl From<ArgumentError> for ExampleError { fn from(err: ArgumentError) -> Self { Self::Argument(err) } }
107/// # impl From<InitializeError> for ExampleError { fn from(err: InitializeError) -> Self { Self::Initialize(err) } }
108/// # fn example() -> Result<(), ExampleError> {
109/// let mut random = Random::new()?;
110/// let max = Max::Integer(10);
111/// let rand = rand(&mut random, max)?;
112/// assert!(matches!(rand, Rand::Integer(x) if x < 10));
113/// # Ok(())
114/// # }
115/// # example().unwrap();
116/// ```
117///
118/// # Errors
119///
120/// When `max` is a negative integer or zero, `rand` returns an
121/// [`ArgumentError`].
122///
123/// When `max` is a negative `f64`, `rand` returns an [`ArgumentError`].
124///
125/// When `max` is a non-finite `f64`, `rand` returns a domain error
126/// [`ArgumentError`].
127#[cfg(feature = "rand-method")]
128#[cfg_attr(docsrs, doc(cfg(feature = "rand-method")))]
129pub fn rand(rng: &mut Random, max: Max) -> Result<Rand, ArgumentError> {
130 let constraint = max;
131 match constraint {
132 Max::Float(max) if !max.is_finite() => {
133 // NOTE: MRI returns `Errno::EDOM` exception class.
134 Err(ArgumentError::domain_error())
135 }
136 Max::Float(max) if max < 0.0 => {
137 let err = ArgumentError::with_rand_max(constraint);
138 Err(err)
139 }
140 Max::Float(0.0) | Max::None => {
141 let number = rng.next_real();
142 Ok(Rand::Float(number))
143 }
144 Max::Float(max) => {
145 let number = rng.random_range(0.0..max);
146 Ok(Rand::Float(number))
147 }
148 Max::Integer(max) if max < 1 => {
149 let err = ArgumentError::with_rand_max(constraint);
150 Err(err)
151 }
152 Max::Integer(max) => {
153 let number = rng.random_range(0..max);
154 Ok(Rand::Integer(number))
155 }
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::{Max, Rand, rand};
162 use crate::Random;
163
164 #[test]
165 fn random_number_domain_error() {
166 let mut random = Random::with_seed(33);
167 assert!(matches!(rand(&mut random, Max::Float(f64::NAN)), Err(err) if err.is_domain_error()));
168 assert!(matches!(rand(&mut random, Max::Float(f64::INFINITY)), Err(err) if err.is_domain_error()));
169 assert!(matches!(rand(&mut random, Max::Float(f64::NEG_INFINITY)), Err(err) if err.is_domain_error()));
170 }
171
172 #[test]
173 fn random_number_in_float_out_float() {
174 let mut random = Random::with_seed(33);
175 assert!(matches!(rand(&mut random, Max::None), Ok(Rand::Float(num)) if num < 1.0));
176 assert!(matches!(
177 rand(&mut random, Max::Float(0.5)),
178 Ok(Rand::Float(num)) if num < 0.5
179 ));
180 assert!(matches!(
181 rand(&mut random, Max::Float(1.0)),
182 Ok(Rand::Float(num)) if num < 1.0
183 ));
184 assert!(matches!(
185 rand(&mut random, Max::Float(9000.63)),
186 Ok(Rand::Float(num)) if num < 9000.63
187 ));
188 assert!(matches!(
189 rand(&mut random, Max::Float(0.0)),
190 Ok(Rand::Float(num)) if num < 1.0
191 ));
192 assert!(matches!(
193 rand(&mut random, Max::Float(-0.0)),
194 Ok(Rand::Float(num)) if num < 1.0
195 ));
196 }
197
198 #[test]
199 fn random_number_in_neg_float_out_err() {
200 let mut random = Random::with_seed(33);
201 assert!(matches!(
202 rand(&mut random, Max::Float(-1.0)), Err(err) if err.message() == "invalid argument"
203 ));
204 }
205
206 #[test]
207 fn random_number_in_neg_integer_out_err() {
208 let mut random = Random::with_seed(33);
209 assert!(matches!(rand(&mut random, Max::Integer(-1)), Err(err) if err.message() == "invalid argument"));
210 }
211
212 #[test]
213 fn random_number_in_zero_integer_out_err() {
214 let mut random = Random::with_seed(33);
215 assert!(matches!(rand(&mut random, Max::Integer(0)), Err(err) if err.message() == "invalid argument"));
216 }
217
218 #[test]
219 fn random_number_in_pos_integer_out_integer() {
220 let mut random = Random::with_seed(33);
221 assert!(matches!(rand(&mut random, Max::Integer(1)), Ok(Rand::Integer(0))));
222 assert!(matches!(
223 rand(&mut random, Max::Integer(9000)),
224 Ok(Rand::Integer(ref num)) if (0..9000).contains(num)
225 ));
226 assert!(matches!(
227 rand(&mut random, Max::Integer(i64::MAX)),
228 Ok(Rand::Integer(num)) if num >= 0
229 ));
230 }
231}