rand/seq/
increasing_uniform.rs

1// Copyright 2018-2023 Developers of the Rand project.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use crate::{Rng, RngCore};
10
11/// Similar to a Uniform distribution,
12/// but after returning a number in the range [0,n], n is increased by 1.
13pub(crate) struct IncreasingUniform<R: RngCore> {
14    pub rng: R,
15    n: u32,
16    // Chunk is a random number in [0, (n + 1) * (n + 2) *..* (n + chunk_remaining) )
17    chunk: u32,
18    chunk_remaining: u8,
19}
20
21impl<R: RngCore> IncreasingUniform<R> {
22    /// Create a dice roller.
23    /// The next item returned will be a random number in the range [0,n]
24    pub fn new(rng: R, n: u32) -> Self {
25        // If n = 0, the first number returned will always be 0
26        // so we don't need to generate a random number
27        let chunk_remaining = if n == 0 { 1 } else { 0 };
28        Self {
29            rng,
30            n,
31            chunk: 0,
32            chunk_remaining,
33        }
34    }
35
36    /// Returns a number in [0,n] and increments n by 1.
37    /// Generates new random bits as needed
38    /// Panics if `n >= u32::MAX`
39    #[inline]
40    pub fn next_index(&mut self) -> usize {
41        let next_n = self.n + 1;
42
43        // There's room for further optimisation here:
44        // random_range uses rejection sampling (or other method; see #1196) to avoid bias.
45        // When the initial sample is biased for range 0..bound
46        // it may still be viable to use for a smaller bound
47        // (especially if small biases are considered acceptable).
48
49        let next_chunk_remaining = self.chunk_remaining.checked_sub(1).unwrap_or_else(|| {
50            // If the chunk is empty, generate a new chunk
51            let (bound, remaining) = calculate_bound_u32(next_n);
52            // bound = (n + 1) * (n + 2) *..* (n + remaining)
53            self.chunk = self.rng.random_range(..bound);
54            // Chunk is a random number in
55            // [0, (n + 1) * (n + 2) *..* (n + remaining) )
56
57            remaining - 1
58        });
59
60        let result = if next_chunk_remaining == 0 {
61            // `chunk` is a random number in the range [0..n+1)
62            // Because `chunk_remaining` is about to be set to zero
63            // we do not need to clear the chunk here
64            self.chunk as usize
65        } else {
66            // `chunk` is a random number in a range that is a multiple of n+1
67            // so r will be a random number in [0..n+1)
68            let r = self.chunk % next_n;
69            self.chunk /= next_n;
70            r as usize
71        };
72
73        self.chunk_remaining = next_chunk_remaining;
74        self.n = next_n;
75        result
76    }
77}
78
79#[inline]
80/// Calculates `bound`, `count` such that bound (m)*(m+1)*..*(m + remaining - 1)
81fn calculate_bound_u32(m: u32) -> (u32, u8) {
82    debug_assert!(m > 0);
83    #[inline]
84    const fn inner(m: u32) -> (u32, u8) {
85        let mut product = m;
86        let mut current = m + 1;
87
88        loop {
89            if let Some(p) = u32::checked_mul(product, current) {
90                product = p;
91                current += 1;
92            } else {
93                // Count has a maximum value of 13 for when min is 1 or 2
94                let count = (current - m) as u8;
95                return (product, count);
96            }
97        }
98    }
99
100    const RESULT2: (u32, u8) = inner(2);
101    if m == 2 {
102        // Making this value a constant instead of recalculating it
103        // gives a significant (~50%) performance boost for small shuffles
104        return RESULT2;
105    }
106
107    inner(m)
108}