dashu_int/third_party/
rand.rs

1//! Random integers generation with the `rand` crate.
2//!
3//! There are four distributions for generating random integers. The first two are [UniformBits],
4//! and [UniformBelow] which limit the bit size or the magnitude of the generated integer.
5//! The other two are [UniformUBig] and [UniformIBig], which supports generating random integers
6//! uniformly in a given range. These traits are also the backends for the [SampleUniform] trait.
7//!
8//! # Examples
9//!
10//! ```
11//! # use dashu_int::{UBig, IBig};
12//! # use rand_v08::{distributions::uniform::Uniform, thread_rng, Rng};
13//! use dashu_base::BitTest;
14//! use dashu_int::rand::{UniformBits, UniformBelow};
15//!
16//! // generate UBigs in a range
17//! let a = thread_rng().gen_range(UBig::from(3u8)..UBig::from(10u8));
18//! let b = thread_rng().sample(Uniform::new(UBig::ZERO, &a));
19//! assert!(a >= UBig::from(3u8) && a < UBig::from(10u8));
20//! assert!(b >= UBig::ZERO && b < a);
21//!
22//! // generate IBigs in a range
23//! let a = thread_rng().gen_range(IBig::from(3)..IBig::from(10));
24//! let b = thread_rng().sample(Uniform::new(IBig::from(-5), &a));
25//! assert!(a >= IBig::from(3) && a < IBig::from(10));
26//! assert!(b >= IBig::from(-5) && b < a);
27//!
28//! // generate UBigs and IBigs with a given bit size limit.
29//! let a: UBig = thread_rng().sample(UniformBits::new(10));
30//! let b: IBig = thread_rng().sample(UniformBits::new(10));
31//! assert!(a.bit_len() <= 10 && b.bit_len() <= 10);
32//!
33//! // generate UBigs and IBigs with a given magnitude limit.
34//! let a: UBig = thread_rng().sample(UniformBelow::new(&10u8.into()));
35//! let b: IBig = thread_rng().sample(UniformBelow::new(&10u8.into()));
36//! assert!(a < UBig::from(10u8));
37//! assert!(b < IBig::from(10) && b > IBig::from(-10));
38//! ```
39
40use crate::{
41    arch::word::{DoubleWord, Word},
42    buffer::Buffer,
43    ibig::IBig,
44    math::ceil_div,
45    ops::UnsignedAbs,
46    primitive::{DWORD_BITS_USIZE, WORD_BITS_USIZE},
47    repr::{Repr, TypedReprRef::*},
48    ubig::UBig,
49};
50
51use dashu_base::Sign;
52use rand_v08::{
53    distributions::uniform::{SampleBorrow, SampleUniform, UniformSampler},
54    prelude::Distribution,
55    Rng,
56};
57
58/// Uniform distribution for both [UBig] and [IBig] specified by bits.
59///
60/// This distribution generate random integers uniformly between `[0, 2^bits)` for [UBig],
61/// between `(-2^bits, 2^bits)` for [IBig].
62pub struct UniformBits {
63    bits: usize,
64}
65
66impl UniformBits {
67    /// Create a [UniformBits] distribution with a given limit on the integer's bit length.
68    #[inline]
69    pub const fn new(bits: usize) -> Self {
70        UniformBits { bits }
71    }
72}
73
74impl Distribution<UBig> for UniformBits {
75    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> UBig {
76        if self.bits == 0 {
77            UBig::ZERO
78        } else if self.bits <= DWORD_BITS_USIZE {
79            let dword: DoubleWord = rng.gen();
80            UBig::from_dword(dword >> (DWORD_BITS_USIZE - self.bits))
81        } else {
82            let num_words = ceil_div(self.bits, WORD_BITS_USIZE);
83            let mut buffer = Buffer::allocate(num_words);
84            buffer.push_zeros(num_words);
85
86            rng.fill(buffer.as_mut());
87
88            let rem = self.bits % WORD_BITS_USIZE;
89            if rem != 0 {
90                *buffer.last_mut().unwrap() >>= WORD_BITS_USIZE - rem;
91            }
92
93            UBig(Repr::from_buffer(buffer))
94        }
95    }
96}
97
98impl Distribution<IBig> for UniformBits {
99    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> IBig {
100        loop {
101            let mag: UBig = self.sample(rng);
102            let neg = rng.gen();
103            if mag.is_zero() && neg {
104                // Reject negative zero so that all possible integers
105                // have the same probability. This branch should happen
106                // very rarely.
107                continue;
108            }
109            break IBig::from_parts(Sign::from(neg), mag);
110        }
111    }
112}
113
114/// Uniform distribution around zero for both [UBig] and [IBig] specified by a limit of the magnitude.
115///
116/// This distribution generate random integers uniformly between `[0, limit)` for [UBig],
117/// between `(-limit, limit)` for [IBig].
118pub struct UniformBelow<'a> {
119    limit: &'a UBig,
120}
121
122impl<'a> UniformBelow<'a> {
123    /// Create a [UniformBelow] distribution with a given limit on the integer's magnitude.
124    #[inline]
125    pub const fn new(limit: &'a UBig) -> Self {
126        Self { limit }
127    }
128}
129
130impl<'a> Distribution<UBig> for UniformBelow<'a> {
131    #[inline]
132    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> UBig {
133        UBig::uniform(self.limit, rng)
134    }
135}
136
137impl<'a> Distribution<IBig> for UniformBelow<'a> {
138    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> IBig {
139        loop {
140            let mag: UBig = UBig::uniform(self.limit, rng);
141            let neg = rng.gen();
142            if mag.is_zero() && neg {
143                // Reject negative zero so that all possible integers
144                // have the same probability. This branch should happen
145                // very rarely.
146                continue;
147            }
148            break IBig::from_parts(Sign::from(neg), mag);
149        }
150    }
151}
152
153impl UBig {
154    /// Random UBig in range [0..range)
155    #[inline]
156    fn uniform<R>(range: &UBig, rng: &mut R) -> UBig
157    where
158        R: Rng + ?Sized,
159    {
160        debug_assert!(!range.is_zero());
161
162        match range.repr() {
163            RefSmall(dword) => UBig::from(rng.gen_range(0..dword)),
164            RefLarge(words) => UBig::uniform_large(words, rng),
165        }
166    }
167
168    /// Random UBig in range [0..words)
169    fn uniform_large<R>(words: &[Word], rng: &mut R) -> UBig
170    where
171        R: Rng + ?Sized,
172    {
173        let mut buffer = Buffer::allocate(words.len());
174        buffer.push_zeros(words.len());
175        while !try_fill_uniform(words, rng, &mut buffer) {
176            // Repeat.
177        }
178        UBig(Repr::from_buffer(buffer))
179    }
180}
181
182/// Try to fill `sample` with random number in range [0..words).
183/// May fail randomly.
184///
185/// Returns true on success.
186fn try_fill_uniform<R>(words: &[Word], rng: &mut R, result: &mut [Word]) -> bool
187where
188    R: Rng + ?Sized,
189{
190    let n = words.len();
191    debug_assert!(n > 0 && result.len() == n);
192    let mut i = n - 1;
193    result[i] = rng.gen_range(0..=words[i]);
194    // With at least 50% probability this loop executes 0 times (and thus doesn't fail).
195    while result[i] == words[i] {
196        if i == 0 {
197            // result == words
198            return false;
199        }
200        i -= 1;
201        result[i] = rng.gen();
202        if result[i] > words[i] {
203            return false;
204        }
205    }
206    rng.fill(&mut result[..i]);
207    true
208}
209
210/// The back-end implementing [UniformSampler] for [UBig].
211///
212/// See the module ([rand][crate::rand]) level documentation for examples.
213pub struct UniformUBig {
214    range: UBig,
215    offset: UBig,
216}
217
218/// Panics when the range input for the random generator in empty
219const fn panic_empty_range() -> ! {
220    panic!("empty range for random generation")
221}
222
223impl UniformSampler for UniformUBig {
224    type X = UBig;
225
226    #[inline]
227    fn new<B1, B2>(low: B1, high: B2) -> UniformUBig
228    where
229        B1: SampleBorrow<UBig>,
230        B2: SampleBorrow<UBig>,
231    {
232        if high.borrow() <= low.borrow() {
233            panic_empty_range()
234        }
235
236        let range = high.borrow() - low.borrow();
237        UniformUBig {
238            range,
239            offset: low.borrow().clone(),
240        }
241    }
242
243    #[inline]
244    fn new_inclusive<B1, B2>(low: B1, high: B2) -> UniformUBig
245    where
246        B1: SampleBorrow<UBig>,
247        B2: SampleBorrow<UBig>,
248    {
249        if high.borrow() < low.borrow() {
250            panic_empty_range()
251        }
252
253        let range = high.borrow() - low.borrow() + UBig::ONE;
254        UniformUBig {
255            range,
256            offset: low.borrow().clone(),
257        }
258    }
259
260    #[inline]
261    fn sample<R>(&self, rng: &mut R) -> UBig
262    where
263        R: Rng + ?Sized,
264    {
265        UBig::uniform(&self.range, rng) + &self.offset
266    }
267}
268
269/// The back-end implementing [UniformSampler] for [IBig].
270///
271/// See the module ([rand][crate::rand]) level documentation for examples.
272pub struct UniformIBig {
273    range: UBig,
274    offset: IBig,
275}
276
277impl UniformSampler for UniformIBig {
278    type X = IBig;
279
280    #[inline]
281    fn new<B1, B2>(low: B1, high: B2) -> UniformIBig
282    where
283        B1: SampleBorrow<IBig>,
284        B2: SampleBorrow<IBig>,
285    {
286        if high.borrow() <= low.borrow() {
287            panic_empty_range()
288        }
289
290        let range = high.borrow() - low.borrow();
291        UniformIBig {
292            range: range.unsigned_abs(),
293            offset: low.borrow().clone(),
294        }
295    }
296
297    #[inline]
298    fn new_inclusive<B1, B2>(low: B1, high: B2) -> UniformIBig
299    where
300        B1: SampleBorrow<IBig>,
301        B2: SampleBorrow<IBig>,
302    {
303        if high.borrow() < low.borrow() {
304            panic_empty_range()
305        }
306
307        let range = high.borrow() - low.borrow() + IBig::ONE;
308        UniformIBig {
309            range: range.unsigned_abs(),
310            offset: low.borrow().clone(),
311        }
312    }
313
314    #[inline]
315    fn sample<R>(&self, rng: &mut R) -> IBig
316    where
317        R: Rng + ?Sized,
318    {
319        IBig::from(UBig::uniform(&self.range, rng)) + &self.offset
320    }
321}
322
323impl SampleUniform for UBig {
324    type Sampler = UniformUBig;
325}
326
327impl SampleUniform for IBig {
328    type Sampler = UniformIBig;
329}