Skip to main content

dashu_int/third_party/
rand.rs

1//! Random integer 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. The
5//! other two are [UniformUBig] and [UniformIBig], which generate random integers uniformly in a
6//! given range; they are also the backends for rand's `SampleUniform` trait.
7//!
8//! The distributions and their sampling algorithms are defined here once, generic over the
9//! [BitRng] trait. Each rand version's `Distribution` / `UniformSampler` / `SampleUniform` impls
10//! live in the `rand_v08` / `rand_v09` / `rand_v010` modules (enable the matching feature); use
11//! `bridge_v08` / `bridge_v09` / `bridge_v010` to adapt that version's RNG into a [BitRng].
12//! See those modules for usage examples.
13
14use crate::{
15    arch::word::{DoubleWord, Word},
16    buffer::Buffer,
17    ibig::IBig,
18    math::ceil_div,
19    primitive::{DWORD_BITS_USIZE, WORD_BITS_USIZE},
20    repr::{Repr, TypedReprRef::*},
21    ubig::UBig,
22};
23use dashu_base::Sign;
24
25/// Version-agnostic source of random machine words.
26///
27/// Each `rand_vXX` module implements this for a thin adapter wrapping that rand version's RNG,
28/// so the generation algorithms live once in this module instead of being copy-pasted per rand
29/// version. Users may also implement it themselves to drive these distributions from a non-`rand`
30/// RNG.
31pub trait BitRng {
32    /// A uniformly random full `Word`.
33    fn next_word(&mut self) -> Word;
34    /// A uniformly random `DoubleWord`.
35    fn next_double_word(&mut self) -> DoubleWord;
36    /// A uniformly random `bool`.
37    fn next_bool(&mut self) -> bool;
38    /// Fill `words` entirely with uniformly random data.
39    fn fill_words(&mut self, words: &mut [Word]);
40    /// Uniformly random `Word` in `0..=high` (inclusive).
41    fn gen_word_inclusive(&mut self, high: Word) -> Word;
42    /// Uniformly random `DoubleWord` in `0..high` (exclusive).
43    fn gen_dword_exclusive(&mut self, high: DoubleWord) -> DoubleWord;
44}
45
46/// Uniform distribution for both [UBig] and [IBig] specified by bits.
47///
48/// This distribution generates random integers uniformly between `[0, 2^bits)` for [UBig], and
49/// between `(-2^bits, 2^bits)` for [IBig].
50pub struct UniformBits {
51    bits: usize,
52}
53
54impl UniformBits {
55    /// Create a [UniformBits] distribution with a given limit on the integer's bit length.
56    #[inline]
57    pub const fn new(bits: usize) -> Self {
58        UniformBits { bits }
59    }
60
61    /// Draw a random [UBig] within this distribution's bit bound.
62    pub fn sample_ubig<R: BitRng + ?Sized>(&self, rng: &mut R) -> UBig {
63        if self.bits == 0 {
64            UBig::ZERO
65        } else if self.bits <= DWORD_BITS_USIZE {
66            let dword: DoubleWord = rng.next_double_word();
67            UBig::from_dword(dword >> (DWORD_BITS_USIZE - self.bits))
68        } else {
69            let num_words = ceil_div(self.bits, WORD_BITS_USIZE);
70            let mut buffer = Buffer::allocate(num_words);
71            buffer.push_zeros(num_words);
72
73            rng.fill_words(buffer.as_mut());
74
75            let rem = self.bits % WORD_BITS_USIZE;
76            if rem != 0 {
77                *buffer.last_mut().unwrap() >>= WORD_BITS_USIZE - rem;
78            }
79
80            UBig(Repr::from_buffer(buffer))
81        }
82    }
83
84    /// Draw a random [IBig] within this distribution's bit bound.
85    pub fn sample_ibig<R: BitRng + ?Sized>(&self, rng: &mut R) -> IBig {
86        loop {
87            let mag: UBig = self.sample_ubig(rng);
88            let neg = rng.next_bool();
89            if mag.is_zero() && neg {
90                // Reject negative zero so that all possible integers have the same
91                // probability. This branch should happen very rarely.
92                continue;
93            }
94            break IBig::from_parts(Sign::from(neg), mag);
95        }
96    }
97}
98
99/// Uniform distribution around zero for both [UBig] and [IBig], bounded by magnitude.
100///
101/// This distribution generates random integers uniformly between `[0, limit)` for [UBig], and
102/// between `(-limit, limit)` for [IBig].
103pub struct UniformBelow<'a> {
104    limit: &'a UBig,
105}
106
107impl<'a> UniformBelow<'a> {
108    /// Create a [UniformBelow] distribution with a given limit on the magnitude.
109    #[inline]
110    pub const fn new(limit: &'a UBig) -> Self {
111        Self { limit }
112    }
113
114    /// Draw a random [UBig] below the limit.
115    #[inline]
116    pub fn sample_ubig<R: BitRng + ?Sized>(&self, rng: &mut R) -> UBig {
117        uniform(self.limit, rng)
118    }
119
120    /// Draw a random [IBig] with magnitude below the limit.
121    pub fn sample_ibig<R: BitRng + ?Sized>(&self, rng: &mut R) -> IBig {
122        loop {
123            let mag: UBig = uniform(self.limit, rng);
124            let neg = rng.next_bool();
125            if mag.is_zero() && neg {
126                // Reject negative zero so that all possible integers have the same
127                // probability. This branch should happen very rarely.
128                continue;
129            }
130            break IBig::from_parts(Sign::from(neg), mag);
131        }
132    }
133}
134
135/// Random [UBig] in range `[0..range)`.
136fn uniform<R: BitRng + ?Sized>(range: &UBig, rng: &mut R) -> UBig {
137    debug_assert!(!range.is_zero());
138
139    match range.repr() {
140        RefSmall(dword) => UBig::from(rng.gen_dword_exclusive(dword)),
141        RefLarge(words) => uniform_large(words, rng),
142    }
143}
144
145/// Random [UBig] in range `[0..words)`.
146fn uniform_large<R: BitRng + ?Sized>(words: &[Word], rng: &mut R) -> UBig {
147    let mut buffer = Buffer::allocate(words.len());
148    buffer.push_zeros(words.len());
149    while !try_fill_uniform(words, rng, &mut buffer) {
150        // Repeat.
151    }
152    UBig(Repr::from_buffer(buffer))
153}
154
155/// Try to fill `result` with a random number in range `[0..words)`. May fail randomly.
156///
157/// Returns true on success.
158fn try_fill_uniform<R: BitRng + ?Sized>(words: &[Word], rng: &mut R, result: &mut [Word]) -> bool {
159    let n = words.len();
160    debug_assert!(n > 0 && result.len() == n);
161    let mut i = n - 1;
162    result[i] = rng.gen_word_inclusive(words[i]);
163    // With at least 50% probability this loop executes 0 times (and thus doesn't fail).
164    while result[i] == words[i] {
165        if i == 0 {
166            // result == words
167            return false;
168        }
169        i -= 1;
170        result[i] = rng.next_word();
171        if result[i] > words[i] {
172            return false;
173        }
174    }
175    rng.fill_words(&mut result[..i]);
176    true
177}
178
179/// The back-end implementing `rand::distributions::uniform::UniformSampler` for [UBig].
180pub struct UniformUBig {
181    pub(crate) range: UBig,
182    pub(crate) offset: UBig,
183}
184
185impl UniformUBig {
186    #[inline]
187    pub(crate) const fn from_parts(range: UBig, offset: UBig) -> Self {
188        UniformUBig { range, offset }
189    }
190
191    /// Draw a random [UBig] from this sampler's `[low, high)` range.
192    #[inline]
193    pub fn sample<R: BitRng + ?Sized>(&self, rng: &mut R) -> UBig {
194        uniform(&self.range, rng) + &self.offset
195    }
196}
197
198/// The back-end implementing `rand::distributions::uniform::UniformSampler` for [IBig].
199pub struct UniformIBig {
200    pub(crate) range: UBig,
201    pub(crate) offset: IBig,
202}
203
204impl UniformIBig {
205    #[inline]
206    pub(crate) const fn from_parts(range: UBig, offset: IBig) -> Self {
207        UniformIBig { range, offset }
208    }
209
210    /// Draw a random [IBig] from this sampler's `[low, high)` range.
211    #[inline]
212    pub fn sample<R: BitRng + ?Sized>(&self, rng: &mut R) -> IBig {
213        IBig::from(uniform(&self.range, rng)) + &self.offset
214    }
215}
216
217/// Adapt a rand 0.8 [`Rng`](rand_v08::Rng) into a [`BitRng`].
218#[cfg(feature = "rand_v08")]
219pub fn bridge_v08<'a, R: rand_v08::Rng + ?Sized>(rng: &'a mut R) -> impl BitRng + 'a {
220    super::rand_v08::RngBridge(rng)
221}
222
223/// Adapt a rand 0.9 [`Rng`](rand_v09::Rng) into a [`BitRng`].
224#[cfg(feature = "rand_v09")]
225pub fn bridge_v09<'a, R: rand_v09::Rng + ?Sized>(rng: &'a mut R) -> impl BitRng + 'a {
226    super::rand_v09::RngBridge(rng)
227}
228
229/// Adapt a rand 0.10 [`Rng`](rand_v010::Rng) into a [`BitRng`].
230#[cfg(feature = "rand_v010")]
231pub fn bridge_v010<'a, R: rand_v010::Rng + ?Sized>(rng: &'a mut R) -> impl BitRng + 'a {
232    super::rand_v010::RngBridge(rng)
233}