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);