Skip to main content

harness/
rand.rs

1//! Randomness utilities.
2
3use std::marker::PhantomData;
4
5use rand::distr::Distribution;
6use rand::{Rng, RngExt};
7
8// ===========================================================================
9// Probe — a range-bounded, boundary-biased u64 sampler.
10// ===========================================================================
11
12/// `i64` boundary values: 0, ±1, and each signed-width min/max.
13const BOUNDARIES_I64: &[i64] = &[
14    i64::MIN,
15    i64::MIN + 1,
16    i32::MIN as i64,
17    i16::MIN as i64,
18    i8::MIN as i64,
19    -1,
20    0,
21    1,
22    i8::MAX as i64,
23    i16::MAX as i64,
24    i32::MAX as i64,
25    i64::MAX - 1,
26    i64::MAX,
27];
28
29/// `f64` boundary values (no NaN/inf — those break frame parsing and belong to a
30/// dedicated malformed-input driver).
31const BOUNDARIES_F64: &[f64] = &[
32    0.0,
33    1.0,
34    -1.0,
35    f64::MIN_POSITIVE,
36    -f64::MIN_POSITIVE,
37    f64::MAX,
38    f64::MIN,
39];
40
41/// A range-bounded, boundary-biased `u64` sampler: about one draw in four a
42/// range edge, the rest log-uniform over the non-zero interior, so `0` appears
43/// only as an edge.
44#[derive(Debug, Clone, Copy)]
45pub struct Probe {
46    min: u64,
47    max: u64,
48}
49
50impl Probe {
51    /// Creates a sampler over `[min, max]`.
52    ///
53    /// # Panics
54    ///
55    /// Panics if `min` exceeds `max`.
56    #[must_use]
57    pub const fn new(min: u64, max: u64) -> Self {
58        assert!(min <= max, "Probe min must not exceed max");
59        Self { min, max }
60    }
61}
62
63impl Distribution<u64> for Probe {
64    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> u64 {
65        if rng.random_ratio(1, 4) {
66            if rng.random_ratio(1, 2) {
67                self.min
68            } else {
69                self.max
70            }
71        } else {
72            // Log-uniform so the interior is covered evenly across magnitudes.
73            let lo = num_traits::cast::<u64, f64>(self.min.max(1)).unwrap_or(1.0).ln();
74            let hi = num_traits::cast::<u64, f64>(self.max.max(1)).unwrap_or(1.0).ln();
75            let exponent = lo + rng.random::<f64>() * (hi - lo);
76            num_traits::cast::<f64, u64>(exponent.exp().round())
77                .unwrap_or(self.max)
78                .clamp(self.min, self.max)
79        }
80    }
81}
82
83// ===========================================================================
84// Wide — a log-uniform magnitude sampler with a random sign and a boundary tail.
85// Draws spread evenly across orders of magnitude so values land in distinct
86// buckets, and ~1/8 of draws are a type boundary so the extremes still appear.
87// The `f64` body spans 1e-30..1e30, the `i64` body spans 1..1e18, and the tail
88// reaches f64::MAX / i64::MAX, which is what overflows a sketch sum or count.
89// ===========================================================================
90
91/// A log-uniform, random-sign magnitude sampler with a boundary tail. See the
92/// section note above.
93#[derive(Debug, Clone, Copy)]
94pub struct Wide;
95
96impl Distribution<f64> for Wide {
97    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> f64 {
98        if rng.random_ratio(1, 8) {
99            return BOUNDARIES_F64[rng.random_range(0..BOUNDARIES_F64.len())];
100        }
101        let mag = 10f64.powf(rng.random_range(-30.0..30.0));
102        if rng.random_range(0..2u8) == 0 {
103            -mag
104        } else {
105            mag
106        }
107    }
108}
109
110impl Distribution<i64> for Wide {
111    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> i64 {
112        if rng.random_ratio(1, 8) {
113            return BOUNDARIES_I64[rng.random_range(0..BOUNDARIES_I64.len())];
114        }
115        let mag = num_traits::cast::<f64, i64>(10f64.powf(rng.random_range(0.0..18.0))).unwrap_or(i64::MAX);
116        if rng.random_range(0..2u8) == 0 {
117            -mag
118        } else {
119            mag
120        }
121    }
122}
123
124// ===========================================================================
125// Boundary<T> — a finite type-boundary sampler: each fixed-width max ±1 and the
126// half-range midpoint ±1, the same idea as the boundary tables above but for one type.
127// ===========================================================================
128
129/// A boundary-value sampler for `T`: each fixed-width max ±1 and the half-range
130/// midpoint ±1. `Boundary::<T>::new().sample(rng)` returns one.
131#[derive(Clone, Copy, Debug, Default)]
132pub struct Boundary<T>(PhantomData<T>);
133
134impl<T> Boundary<T> {
135    /// A boundary sampler for `T`.
136    #[must_use]
137    pub const fn new() -> Self {
138        Boundary(PhantomData)
139    }
140}
141
142const BOUNDARY_U8: &[u8] = &[0, 1, 2, 126, 127, 128, 129, 254, 255];
143
144impl Distribution<u8> for Boundary<u8> {
145    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> u8 {
146        BOUNDARY_U8[rng.random_range(0..BOUNDARY_U8.len())]
147    }
148}
149
150const BOUNDARY_U64: &[u64] = &[
151    0,
152    1,
153    2,
154    u8::MAX as u64 - 1,
155    u8::MAX as u64,
156    u8::MAX as u64 + 1,
157    u16::MAX as u64 - 1,
158    u16::MAX as u64,
159    u16::MAX as u64 + 1,
160    u32::MAX as u64 - 1,
161    u32::MAX as u64,
162    u32::MAX as u64 + 1,
163    u64::MAX / 2 - 1,
164    u64::MAX / 2,
165    u64::MAX / 2 + 1,
166    u64::MAX - 1,
167    u64::MAX,
168];
169
170impl Distribution<u64> for Boundary<u64> {
171    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> u64 {
172        BOUNDARY_U64[rng.random_range(0..BOUNDARY_U64.len())]
173    }
174}
175
176const BOUNDARY_I64: &[i64] = &[
177    i64::MIN,
178    i64::MIN + 1,
179    i64::MIN / 2 - 1,
180    i64::MIN / 2,
181    i64::MIN / 2 + 1,
182    i32::MIN as i64,
183    i16::MIN as i64,
184    i8::MIN as i64,
185    -1,
186    0,
187    1,
188    i8::MAX as i64,
189    i16::MAX as i64,
190    i32::MAX as i64,
191    i64::MAX / 2 - 1,
192    i64::MAX / 2,
193    i64::MAX / 2 + 1,
194    i64::MAX - 1,
195    i64::MAX,
196];
197
198impl Distribution<i64> for Boundary<i64> {
199    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> i64 {
200        BOUNDARY_I64[rng.random_range(0..BOUNDARY_I64.len())]
201    }
202}