dashu_ratio/third_party/rand.rs
1//! Random rational numbers generation with the `rand` crate.
2//!
3//! There are two new distributions for generating random floats. The first one is [Uniform01],
4//! which supports generating rationals between 0 and 1. This is also the underlying implementation
5//! of the builtin `rand` distributions [Standard], [Open01], [OpenClosed01]. The other one is
6//! [UniformRBig], which supports generating rationals in a certain range. This is also the
7//! backend for the [SampleUniform] trait.
8//!
9//! Note that [Uniform01] supports generating both [RBig] and [Relaxed], but [UniformRBig] currently
10//! only supports generating [RBig].
11//!
12//! # Examples
13//!
14//! ```
15//! use dashu_ratio::{RBig, Relaxed, rand::Uniform01};
16//! # use rand_v08::{distributions::uniform::Uniform, thread_rng, Rng};
17//!
18//! // generate RBigs in a [0, 1) or [0, 1] with a given precision
19//! let a: RBig = thread_rng().sample(Uniform01::new(&10u8.into()));
20//! let b: Relaxed = thread_rng().sample(Uniform01::new_closed(&10u8.into()));
21//! let c: RBig = thread_rng().gen(); // the default distribution generates in [0, 1)
22//! assert!(a >= RBig::ZERO && a < RBig::ONE);
23//! assert!(b >= Relaxed::ZERO && b <= Relaxed::ONE);
24//! assert!(c >= RBig::ZERO && c < RBig::ONE);
25//!
26//! // generate RBigs in a range
27//! let a = thread_rng().gen_range(RBig::from(3)..RBig::from(10));
28//! let b = thread_rng().sample(Uniform::new(RBig::from(-5), &a));
29//! assert!(a >= RBig::from(3) && a < RBig::from(10));
30//! assert!(b >= RBig::from(-5) && b < a);
31//! ```
32//!
33//! # Denominator
34//!
35//! The denominator of a rational generated by different distributions is explained below:
36//! * [Uniform01] will generate rationals with a denominator decided by the constructor.
37//! * [Standard], [Open01], [OpenClosed01] will generate rationals with [DoubleWord::MAX] as
38//! the denominator.
39//! * [UniformRBig] will generate rationals with the denominator being the least common multiple
40//! of the denominators of the interval boundaries.
41//!
42//! Note that in the current implementation, the denominator of a rational generated in a closed interval
43//! will be less by one than that generated in an open or half-open interval.
44//!
45
46use crate::{rbig::RBig, repr::Repr, Relaxed};
47
48use dashu_base::Gcd;
49use dashu_int::{
50 rand::{UniformBelow, UniformIBig},
51 DoubleWord, IBig, UBig,
52};
53use rand_v08::{
54 distributions::{
55 uniform::{SampleBorrow, SampleUniform, UniformSampler},
56 Open01, OpenClosed01, Standard,
57 },
58 prelude::Distribution,
59 Rng,
60};
61
62/// A uniform distribution between 0 and 1. It can be used to replace the [Standard], [Open01],
63/// [OpenClosed01] distributions from the `rand` crate when you want to customize the denominator
64/// limit of the generated float number.
65pub struct Uniform01<'a> {
66 limit: &'a UBig,
67 include_zero: bool, // whether include the zero
68 include_one: bool, // whether include the one
69}
70
71impl<'a> Uniform01<'a> {
72 /// Create a uniform distribution in `[0, 1)` with a given limit of the denominator.
73 #[inline]
74 pub fn new(limit: &'a UBig) -> Self {
75 Self {
76 limit,
77 include_zero: true,
78 include_one: false,
79 }
80 }
81
82 /// Create a uniform distribution in `[0, 1]` with a given limit of the denominator.
83 #[inline]
84 pub fn new_closed(limit: &'a UBig) -> Self {
85 Self {
86 limit,
87 include_zero: true,
88 include_one: true,
89 }
90 }
91
92 /// Create a uniform distribution in `(0, 1)` with a given limit of the denominator.
93 #[inline]
94 pub fn new_open(limit: &'a UBig) -> Self {
95 Self {
96 limit,
97 include_zero: false,
98 include_one: false,
99 }
100 }
101
102 /// Create a uniform distribution in `(0, 1]` with a given limit of the denominator.
103 #[inline]
104 pub fn new_open_closed(limit: &'a UBig) -> Self {
105 Self {
106 limit,
107 include_zero: false,
108 include_one: true,
109 }
110 }
111}
112
113impl<'a> Distribution<Repr> for Uniform01<'a> {
114 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Repr {
115 match (self.include_zero, self.include_one) {
116 (true, false) => {
117 // sample in [0, 1)
118 let num: UBig = UniformBelow::new(self.limit).sample(rng);
119 Repr {
120 numerator: num.into(),
121 denominator: self.limit.clone(),
122 }
123 }
124 (true, true) => {
125 // sample in [0, 1]
126 let num: UBig = UniformBelow::new(self.limit).sample(rng);
127 Repr {
128 numerator: num.into(),
129 denominator: self.limit.clone() - UBig::ONE,
130 }
131 }
132 (false, false) => {
133 // sample in (0, 1)
134 let num = loop {
135 // simply reject zero
136 let n: UBig = UniformBelow::new(self.limit).sample(rng);
137 if !n.is_zero() {
138 break n;
139 }
140 };
141 Repr {
142 numerator: num.into(),
143 denominator: self.limit.clone(),
144 }
145 }
146 (false, true) => {
147 // sample in (0, 1]
148 let num: UBig = UniformBelow::new(self.limit).sample(rng);
149 Repr {
150 numerator: (num + UBig::ONE).into(),
151 denominator: self.limit.clone(),
152 }
153 }
154 }
155 }
156}
157
158impl<'a> Distribution<RBig> for Uniform01<'a> {
159 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> RBig {
160 RBig(Distribution::<Repr>::sample(self, rng).reduce())
161 }
162}
163
164impl<'a> Distribution<Relaxed> for Uniform01<'a> {
165 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Relaxed {
166 Relaxed(Distribution::<Repr>::sample(self, rng).reduce2())
167 }
168}
169
170/// The back-end implementing [UniformSampler] for [RBig].
171///
172/// See the module ([rand][crate::rand]) level documentation for examples.
173pub struct UniformRBig {
174 num_sampler: UniformIBig,
175 den: UBig,
176}
177
178impl UniformRBig {
179 // parse the bounds `(low, high)` to a `(numerator low, numerator high, denominator)`
180 fn parse_bounds<B1, B2>(low: B1, high: B2) -> (IBig, IBig, UBig)
181 where
182 B1: SampleBorrow<RBig> + Sized,
183 B2: SampleBorrow<RBig> + Sized,
184 {
185 let (low_d, high_d) = (low.borrow().denominator(), high.borrow().denominator());
186 let g = low_d.gcd(high_d);
187 let low_n = high_d / &g * low.borrow().numerator();
188 let high_n = low_d / &g * high.borrow().numerator();
189 let den = low_d / g * high_d;
190 (low_n, high_n, den)
191 }
192}
193
194impl UniformSampler for UniformRBig {
195 type X = RBig;
196
197 #[inline]
198 fn new<B1, B2>(low: B1, high: B2) -> Self
199 where
200 B1: SampleBorrow<Self::X> + Sized,
201 B2: SampleBorrow<Self::X> + Sized,
202 {
203 let (low_n, high_n, den) = Self::parse_bounds(low, high);
204 UniformRBig {
205 num_sampler: UniformIBig::new(low_n, high_n),
206 den,
207 }
208 }
209
210 #[inline]
211 fn new_inclusive<B1, B2>(low: B1, high: B2) -> Self
212 where
213 B1: SampleBorrow<Self::X> + Sized,
214 B2: SampleBorrow<Self::X> + Sized,
215 {
216 let (low_n, high_n, den) = Self::parse_bounds(low, high);
217 UniformRBig {
218 num_sampler: UniformIBig::new_inclusive(low_n, high_n),
219 den,
220 }
221 }
222
223 #[inline]
224 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> RBig {
225 RBig::from_parts(self.num_sampler.sample(rng), self.den.clone())
226 }
227}
228
229impl SampleUniform for RBig {
230 type Sampler = UniformRBig;
231}
232
233macro_rules! impl_builtin_distr {
234 (impl $trait:ident for $t:ty, $ctor:ident) => {
235 impl Distribution<$t> for $trait {
236 #[inline]
237 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> $t {
238 let limit = UBig::from_dword(DoubleWord::MAX);
239 Uniform01::$ctor(&limit).sample(rng)
240 }
241 }
242 };
243}
244
245impl_builtin_distr!(impl Standard for RBig, new);
246impl_builtin_distr!(impl Standard for Relaxed, new);
247impl_builtin_distr!(impl Open01 for RBig, new_open);
248impl_builtin_distr!(impl Open01 for Relaxed, new_open);
249impl_builtin_distr!(impl OpenClosed01 for RBig, new_open_closed);
250impl_builtin_distr!(impl OpenClosed01 for Relaxed, new_open_closed);