use std::cell::Cell;
use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hasher};
use std::time::Duration;
pub(crate) fn capped_exponential(base: Duration, factor: f64, n: u32, cap: Duration) -> Duration {
if base.is_zero() {
return Duration::ZERO;
}
let factor = if factor.is_finite() && factor > 1.0 {
factor
} else {
1.0
};
if factor == 1.0 || n == 0 {
return base.min(cap);
}
let exponent = n.min(i32::MAX as u32) as i32;
let secs = (base.as_secs_f64() * factor.powi(exponent)).min(cap.as_secs_f64());
Duration::try_from_secs_f64(secs).unwrap_or(cap)
}
pub(crate) fn unit_random_f64() -> f64 {
thread_local! {
static STATE: Cell<u64> = Cell::new(seed());
}
STATE.with(|state| {
let mut x = state.get();
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
state.set(x);
(x >> 11) as f64 / (1u64 << 53) as f64
})
}
fn seed() -> u64 {
let mut hasher = RandomState::new().build_hasher();
hasher.write_u64(0x9E37_79B9_7F4A_7C15);
hasher.finish() | 1 }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero_base_never_waits() {
assert_eq!(
capped_exponential(Duration::ZERO, 2.0, 0, Duration::from_secs(1)),
Duration::ZERO
);
assert_eq!(
capped_exponential(Duration::ZERO, 2.0, u32::MAX, Duration::from_secs(1)),
Duration::ZERO
);
}
#[test]
fn exponential_growth_and_cap() {
let base = Duration::from_millis(100);
let cap = Duration::from_millis(450);
assert_eq!(capped_exponential(base, 2.0, 0, cap), base);
assert_eq!(
capped_exponential(base, 2.0, 1, cap),
Duration::from_millis(200)
);
assert_eq!(
capped_exponential(base, 2.0, 2, cap),
Duration::from_millis(400)
);
assert_eq!(capped_exponential(base, 2.0, 3, cap), cap); assert_eq!(capped_exponential(base, 2.0, 50, cap), cap); }
#[test]
fn non_finite_or_sub_unit_factor_is_fixed() {
let base = Duration::from_millis(100);
let cap = Duration::from_secs(5);
for bad in [
f64::NAN,
f64::INFINITY,
f64::NEG_INFINITY,
-1.0,
0.0,
0.5,
1.0,
] {
assert_eq!(capped_exponential(base, bad, 0, cap), base, "bad = {bad}");
assert_eq!(capped_exponential(base, bad, 3, cap), base, "bad = {bad}");
}
}
#[test]
fn unit_random_f64_is_in_range() {
for _ in 0..256 {
let u = unit_random_f64();
assert!((0.0..1.0).contains(&u), "out of [0, 1): {u}");
}
}
}