use super::ValueGenerator;
pub struct StepGenerator {
start: f64,
step_size: f64,
max: Option<f64>,
}
impl StepGenerator {
pub fn new(start: f64, step_size: f64, max: Option<f64>) -> Self {
Self {
start,
step_size,
max,
}
}
}
impl ValueGenerator for StepGenerator {
fn value(&self, tick: u64) -> f64 {
let raw = tick as f64 * self.step_size;
match self.max {
Some(m) if m > self.start => {
let range = m - self.start;
self.start + (raw.rem_euclid(range))
}
_ => self.start + raw,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tick_zero_returns_start() {
let gen = StepGenerator::new(5.0, 1.0, None);
assert_eq!(gen.value(0), 5.0);
}
#[test]
fn tick_zero_returns_start_with_max() {
let gen = StepGenerator::new(10.0, 2.0, Some(100.0));
assert_eq!(gen.value(0), 10.0);
}
#[test]
fn linear_increase_without_max() {
let gen = StepGenerator::new(0.0, 1.0, None);
for tick in 0..10u64 {
assert_eq!(gen.value(tick), tick as f64);
}
}
#[test]
fn linear_increase_with_nonzero_start() {
let gen = StepGenerator::new(100.0, 1.0, None);
assert_eq!(gen.value(0), 100.0);
assert_eq!(gen.value(1), 101.0);
assert_eq!(gen.value(5), 105.0);
}
#[test]
fn wrap_around_at_max_boundary() {
let gen = StepGenerator::new(0.0, 1.0, Some(3.0));
assert_eq!(gen.value(0), 0.0);
assert_eq!(gen.value(1), 1.0);
assert_eq!(gen.value(2), 2.0);
assert_eq!(gen.value(3), 0.0);
assert_eq!(gen.value(4), 1.0);
assert_eq!(gen.value(5), 2.0);
assert_eq!(gen.value(6), 0.0);
}
#[test]
fn wrap_around_with_nonzero_start() {
let gen = StepGenerator::new(10.0, 1.0, Some(13.0));
assert_eq!(gen.value(0), 10.0);
assert_eq!(gen.value(1), 11.0);
assert_eq!(gen.value(2), 12.0);
assert_eq!(gen.value(3), 10.0);
assert_eq!(gen.value(4), 11.0);
}
#[test]
fn max_equal_to_start_acts_as_no_wrap() {
let gen = StepGenerator::new(5.0, 1.0, Some(5.0));
assert_eq!(gen.value(0), 5.0);
assert_eq!(gen.value(1), 6.0);
assert_eq!(gen.value(10), 15.0);
}
#[test]
fn max_less_than_start_acts_as_no_wrap() {
let gen = StepGenerator::new(10.0, 1.0, Some(5.0));
assert_eq!(gen.value(0), 10.0);
assert_eq!(gen.value(5), 15.0);
}
#[test]
fn max_none_is_unbounded() {
let gen = StepGenerator::new(0.0, 1.0, None);
assert_eq!(gen.value(1_000_000), 1_000_000.0);
}
#[test]
fn fractional_step_size() {
let gen = StepGenerator::new(0.0, 0.5, None);
assert_eq!(gen.value(0), 0.0);
assert_eq!(gen.value(1), 0.5);
assert_eq!(gen.value(2), 1.0);
assert_eq!(gen.value(3), 1.5);
}
#[test]
fn fractional_step_with_wrap() {
let gen = StepGenerator::new(0.0, 0.5, Some(2.0));
assert_eq!(gen.value(0), 0.0);
assert_eq!(gen.value(1), 0.5);
assert_eq!(gen.value(2), 1.0);
assert_eq!(gen.value(3), 1.5);
assert_eq!(gen.value(4), 0.0);
}
#[test]
fn determinism_same_tick_same_value() {
let gen = StepGenerator::new(0.0, 1.0, Some(100.0));
let v1 = gen.value(42);
let v2 = gen.value(42);
assert_eq!(v1, v2, "same tick must always produce the same value");
}
#[test]
fn large_tick_values_do_not_panic() {
let gen = StepGenerator::new(0.0, 1.0, Some(1000.0));
let v = gen.value(u64::MAX / 2);
assert!(v.is_finite(), "large tick must produce a finite value");
}
#[test]
fn large_tick_unbounded_does_not_panic() {
let gen = StepGenerator::new(0.0, 1.0, None);
let v = gen.value(1_000_000_000);
assert_eq!(v, 1_000_000_000.0);
}
#[test]
fn negative_step_size() {
let gen = StepGenerator::new(100.0, -1.0, None);
assert_eq!(gen.value(0), 100.0);
assert_eq!(gen.value(1), 99.0);
assert_eq!(gen.value(5), 95.0);
}
#[test]
fn negative_step_with_wrap_stays_in_range() {
let gen = StepGenerator::new(0.0, -1.0, Some(3.0));
for tick in 0..20 {
let v = gen.value(tick);
assert!(
v >= 0.0 && v < 3.0,
"value {v} at tick {tick} must be in [0.0, 3.0)"
);
}
}
#[test]
fn zero_step_size_returns_start() {
let gen = StepGenerator::new(42.0, 0.0, None);
assert_eq!(gen.value(0), 42.0);
assert_eq!(gen.value(100), 42.0);
}
}