pub const DEFAULT_COFACTOR: f32 = 150.0;
#[inline]
pub fn forward(x: f32, cofactor: f32) -> f32 {
(x / cofactor).asinh()
}
#[inline]
pub fn inverse(y: f32, cofactor: f32) -> f32 {
cofactor * y.sinh()
}
pub fn forward_inverse_error(samples: &[f32], cofactor: f32) -> (f32, f32) {
let mut max_abs = 0f32;
let mut max_rel = 0f32;
for &x in samples {
let y = forward(x, cofactor);
let r = inverse(y, cofactor);
let abs = (r - x).abs();
max_abs = max_abs.max(abs);
if x.abs() > 1e-3 {
max_rel = max_rel.max(abs / x.abs());
}
}
(max_abs, max_rel)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn forward_inverse_identity_within_eps() {
let samples: Vec<f32> = (-1000..1000).map(|i| (i as f32) * 50.0).collect();
let (_abs, rel) = forward_inverse_error(&samples, DEFAULT_COFACTOR);
assert!(rel < 1e-5, "max rel err = {rel}");
}
#[test]
fn handles_negatives() {
let y = forward(-100.0, 5.0);
assert!(y < 0.0);
let r = inverse(y, 5.0);
assert!((r - (-100.0)).abs() < 1e-3);
}
#[test]
fn near_zero_is_near_linear() {
let c = 150.0;
for &x in &[-1.0f32, -0.1, 0.0, 0.1, 1.0] {
let y = forward(x, c);
assert!((c * y - x).abs() < 1e-3);
}
}
}