use crate::DitherSource;
#[derive(Clone, Debug)]
pub struct GoldenRatioDither {
state: f32,
}
const PHI: f32 = 0.618_033_98_f32;
impl GoldenRatioDither {
#[inline]
pub fn new(initial_state: f32) -> Self {
Self { state: initial_state.abs().fract() }
}
#[inline]
pub fn from_ids(layer_id: u32, channel_id: u32) -> Self {
let s = ((layer_id as f32) * PHI + (channel_id as f32) * PHI * PHI).fract();
Self { state: s }
}
#[inline]
pub fn state(&self) -> f32 {
self.state
}
}
impl DitherSource for GoldenRatioDither {
#[inline]
fn next_unit(&mut self) -> f32 {
self.state = (self.state + PHI).fract();
self.state - 0.5
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::DitherSource;
#[test]
fn output_is_in_range() {
let mut d = GoldenRatioDither::new(0.0);
for _ in 0..10_000 {
let v = d.next_unit();
assert!(v >= -0.5 && v <= 0.5, "out of range: {v}");
}
}
#[test]
fn mean_is_near_zero() {
let mut d = GoldenRatioDither::new(0.0);
let n = 100_000;
let mean: f32 = (0..n).map(|_| d.next_unit()).sum::<f32>() / n as f32;
assert!(mean.abs() < 0.01, "mean too large: {mean}");
}
#[test]
fn from_ids_decorrelates() {
let mut d0 = GoldenRatioDither::from_ids(0, 0);
let mut d1 = GoldenRatioDither::from_ids(1, 7);
let v0 = d0.next_unit();
let v1 = d1.next_unit();
assert!((v0 - v1).abs() > 1e-4, "distinct seeds should produce distinct first values");
}
#[test]
fn deterministic_across_calls() {
let mut d1 = GoldenRatioDither::new(0.123);
let mut d2 = GoldenRatioDither::new(0.123);
for _ in 0..1000 {
assert_eq!(d1.next_unit(), d2.next_unit());
}
}
}