const MAX_ABS: f32 = i16::MAX as f32;
pub(crate) fn choose_scale(regret: &[f32]) -> f32 {
let peak = regret.iter().fold(0.0f32, |m, &r| m.max(r.abs()));
(peak / MAX_ABS).max(f32::MIN_POSITIVE)
}
pub(crate) fn encode(r: f32, scale: f32) -> i16 {
let q = (r / scale).round();
#[allow(clippy::cast_possible_truncation)]
if q >= MAX_ABS {
i16::MAX
} else if q <= i16::MIN as f32 {
i16::MIN
} else {
q as i16
}
}
pub(crate) fn decode(q: i16, scale: f32) -> f32 {
q as f32 * scale
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scale_keeps_codes_in_range_and_round_trips() {
let regret = [0.0f32, 1234.5, -9999.0, 42.0];
let s = choose_scale(®ret);
for &r in ®ret {
let q = encode(r, s);
let back = decode(q, s);
assert!((r - back).abs() <= s + 1e-3, "{r} vs {back} (s={s})");
}
}
#[test]
fn overflow_saturates_not_wraps() {
let s = 1.0;
assert_eq!(encode(1e9, s), i16::MAX);
assert_eq!(encode(-1e9, s), i16::MIN);
}
#[test]
fn saturation_boundaries() {
let s = 1.0;
assert_eq!(encode(32767.0, s), i16::MAX); assert_eq!(encode(32767.5, s), i16::MAX); assert_eq!(encode(32766.5, s), i16::MAX); assert_eq!(encode(32765.0, s), 32765i16); assert_eq!(encode(-32768.0, s), i16::MIN); assert_eq!(encode(-32767.0, s), -32767i16); }
#[test]
fn all_zero_regret_has_finite_positive_scale() {
let s = choose_scale(&[0.0, 0.0, 0.0]);
assert!(s > 0.0 && s.is_finite());
}
}