criterion/stats/
rand_util.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
use rand_core::{RngCore, SeedableRng};
use rand_os::OsRng;
use rand_xoshiro::Xoshiro256StarStar;

use std::cell::RefCell;

pub type Rng = Xoshiro256StarStar;

thread_local! {
    static SEED_RAND: RefCell<OsRng> = RefCell::new(OsRng::new().unwrap());
}

pub fn new_rng() -> Rng {
    SEED_RAND.with(|r| {
        let mut seed = [0u8; 32];
        r.borrow_mut().fill_bytes(&mut seed);
        Xoshiro256StarStar::from_seed(seed)
    })
}

/// Source of uniform random numbers within a range of usize values.
pub struct Range {
    low: usize,
    range: usize,
    zone: usize,
}
impl Range {
    pub fn new_exclusive(low: usize, high: usize) -> Range {
        Range::new_inclusive(low, high - 1)
    }

    pub fn new_inclusive(low: usize, high: usize) -> Range {
        let max = ::std::usize::MAX;
        assert!(low < high);
        assert!(high < max);
        let range = high.wrapping_sub(low).wrapping_add(1);
        let ints_to_reject = (max - range + 1) % range;
        let zone = max - ints_to_reject;

        Range { low, range, zone }
    }

    pub fn sample<R: RngCore>(&self, rng: &mut R) -> usize {
        loop {
            let value: usize = rng.next_u64() as usize;

            if value <= self.zone {
                return (value % self.range) + self.low;
            }
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use quickcheck::TestResult;
    use std::cmp;

    quickcheck! {
        fn range_returns_samples_in_range(low: usize, high: usize) -> TestResult {
            let (low, high) = (cmp::min(low, high), cmp::max(low, high));
            if (high - low) < 2 || high == ::std::usize::MAX {
                return TestResult::discard();
            }

            let mut rng = new_rng();
            let range = Range::new_inclusive(low, high);

            for _ in 0..1000 {
                let value = range.sample(&mut rng);
                if !(low <= value && value <= high) {
                    return TestResult::from_bool(false);
                }
            }

            return TestResult::from_bool(true);
        }
    }

    struct CountingRng {
        count: u64,
    }
    impl ::rand_core::RngCore for CountingRng {
        fn next_u32(&mut self) -> u32 {
            self.next_u64() as u32
        }

        fn next_u64(&mut self) -> u64 {
            let value = self.count;
            self.count = self.count.wrapping_add(1);
            value
        }

        fn fill_bytes(&mut self, _dest: &mut [u8]) {
            unimplemented!()
        }

        fn try_fill_bytes(
            &mut self,
            _dest: &mut [u8],
        ) -> ::std::result::Result<(), ::rand_core::Error> {
            unimplemented!()
        }
    }

    // These are arbitrary
    const SIZE: usize = 17;
    const ROUNDS: usize = 200;

    #[test]
    fn range_is_uniform() {
        let mut rng = CountingRng { count: 0 };
        let range = Range::new_exclusive(0, SIZE);
        let mut array = [0usize; SIZE];

        for _ in 0..(ROUNDS * SIZE) {
            let index = range.sample(&mut rng);
            array[index] += 1;
        }

        assert_eq!([ROUNDS; SIZE], array);
    }

    #[test]
    fn range_is_uniform_outside_of_zone() {
        // Check that range is still uniform when provided with numbers near u64::MAX.
        let mut rng = CountingRng {
            count: ::std::u64::MAX - ((SIZE * ROUNDS) / 2) as u64,
        };
        let range = Range::new_exclusive(0, SIZE);
        let mut array = [0usize; SIZE];

        for _ in 0..(ROUNDS * SIZE) {
            let index = range.sample(&mut rng);
            array[index] += 1;
        }

        assert_eq!([ROUNDS; SIZE], array);
    }
}