use rand_core::RngCore;
use serde::{Deserialize, Serialize};
use crate::{Error, dist};
use std::fmt;
use self::dist::Dist;
#[derive(Debug, Eq, Hash, PartialEq, Clone, Copy, Serialize, Deserialize)]
pub enum Operation {
Increment,
Decrement,
Set,
}
#[derive(PartialEq, Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Counter {
pub operation: Operation,
pub dist: Option<Dist>,
pub copy: bool,
}
impl fmt::Display for Counter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:#?}")
}
}
impl Counter {
pub fn new(operation: Operation) -> Self {
Counter {
operation,
dist: None,
copy: false,
}
}
pub fn new_dist(operation: Operation, dist: Dist) -> Self {
Counter {
operation,
dist: Some(dist),
copy: false,
}
}
pub fn new_copy(operation: Operation) -> Self {
Counter {
operation,
dist: None,
copy: true,
}
}
pub fn sample_value<R: RngCore>(&self, rng: &mut R) -> u64 {
const MAX_SAFE_F64_TO_U64: f64 = u64::MAX as f64;
match self.dist {
None => 1,
Some(dist) => {
let sampled = dist.sample(rng);
if !sampled.is_finite() || sampled < 0.0 {
0
} else if sampled > MAX_SAFE_F64_TO_U64 {
u64::MAX
} else {
sampled as u64
}
}
}
}
pub fn validate(&self) -> Result<(), Error> {
if let Some(dist) = self.dist {
dist.validate()?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::{counter::*, dist::DistType};
#[test]
fn validate_counter_update() {
let mut cu = Counter::new_dist(
Operation::Increment,
Dist {
dist: DistType::Uniform {
low: 10.0,
high: 10.0,
},
start: 0.0,
max: 0.0,
},
);
let r = cu.validate();
assert!(r.is_ok());
cu.dist = Some(Dist {
dist: DistType::Uniform {
low: 15.0, high: 5.0,
},
start: 0.0,
max: 0.0,
});
let r = cu.validate();
assert!(r.is_err());
cu.dist = None;
let r = cu.validate();
assert!(r.is_ok());
assert_eq!(cu.sample_value(&mut rand::rng()), 1);
cu.copy = true;
let r = cu.validate();
assert!(r.is_ok());
}
#[test]
fn sample_value_overflow_protection() {
use crate::dist::{Dist, DistType};
let cu = Counter::new_dist(
Operation::Increment,
Dist {
dist: DistType::Uniform {
low: f64::MAX,
high: f64::MAX,
},
start: 0.0,
max: 0.0,
},
);
let sampled = cu.sample_value(&mut rand::rng());
assert_eq!(sampled, u64::MAX);
let cu_negative = Counter::new_dist(
Operation::Increment,
Dist {
dist: DistType::Uniform {
low: -1000.0,
high: -500.0,
},
start: 0.0,
max: 0.0,
},
);
let sampled_negative = cu_negative.sample_value(&mut rand::rng());
assert_eq!(sampled_negative, 0);
let cu_nan = Counter::new_dist(
Operation::Increment,
Dist {
dist: DistType::Normal {
mean: f64::NAN,
stdev: 1.0,
},
start: 0.0,
max: 0.0,
},
);
let sampled_nan = cu_nan.sample_value(&mut rand::rng());
assert_eq!(sampled_nan, 0);
}
}