use std::any::type_name;
pub trait ToF64 {
fn to_f64(self) -> f64;
}
pub trait FromF64 {
fn from_f64(value: f64) -> Self;
}
pub trait Scalable {
fn scale<R: FromF64>(
self,
from_low: impl ToF64,
from_high: impl ToF64,
to_low: impl ToF64,
to_high: impl ToF64,
) -> R;
}
macro_rules! impl_from_scalable {
($($variant:ty),*) => {
$(
impl Scalable for $variant {
fn scale<R: FromF64>(self, from_low: impl ToF64, from_high: impl ToF64, to_low: impl ToF64, to_high: impl ToF64) -> R {
let from_low = from_low.to_f64();
let from_high = from_high.to_f64();
let to_low = to_low.to_f64();
let to_high = to_high.to_f64();
let result = (self as f64 - from_low) * (to_high - to_low) / (from_high - from_low) + to_low;
R::from_f64(result)
}
}
impl ToF64 for $variant {
fn to_f64(self) -> f64 {
self as f64
}
}
impl FromF64 for $variant {
fn from_f64(value: f64) -> Self {
match type_name::<$variant>() {
"f32" | "f64" => value as $variant,
_ => value.round() as $variant
}
}
}
)*
};
}
impl_from_scalable!(u8, u16, u32, u64, i8, i16, i32, i64, f32, f64);
#[cfg(test)]
mod tests {
use super::Scalable;
#[test]
fn test_scale_unsigned() {
assert_eq!(50.scale::<u8>(0, 100, 0, 255), 128);
assert_eq!(0.scale::<u8>(0, 100, 0, 255), 0);
assert_eq!(100.scale::<u8>(0, 100, 0, 255), 255);
assert_eq!(0.scale::<u16>(0, 100, 180, 0), 180);
assert_eq!(0.75.scale::<u16>(0, 1, 0, 180), 135);
assert_eq!(0.75.scale::<u16>(0, 1, 180, 0), 45);
assert_eq!(100.scale::<u16>(0, 100, 180, 0), 0);
assert_eq!(0.scale::<u32>(0, 100, 180, 0), 180);
assert_eq!(0.75.scale::<u32>(0, 1, 0, 180), 135);
assert_eq!(0.75.scale::<u32>(0, 1, 180, 0), 45);
assert_eq!(100.scale::<u32>(0, 100, 180, 0), 0);
assert_eq!(0.scale::<u64>(100, 0, 180, 0), 0);
assert_eq!(0.75.scale::<u64>(1, 0, 0, 180), 45);
assert_eq!(0.75.scale::<u64>(1, 0, 180, 0), 135);
assert_eq!(100.scale::<u64>(100, 0, 180, 0), 180);
}
#[test]
fn test_scale_signed() {
assert_eq!(50.scale::<i8>(0, 100, -50, 50), 0);
assert_eq!(0.scale::<i8>(0, 100, -50, 0), -50);
assert_eq!(100.scale::<i8>(0, 100, -50, 50), 50);
assert_eq!(0.scale::<i16>(0, 100, 180, 0), 180);
assert_eq!((-0.75).scale::<i16>(0, -1, 0, 180), 135);
assert_eq!((-0.25).scale::<i16>(-1, 0, 180, 0), 45);
assert_eq!(100.scale::<i16>(0, 100, 180, 0), 0);
assert_eq!(0.scale::<i32>(0, 100, 180, 0), 180);
assert_eq!(0.75.scale::<i32>(0, 1, 0, 180), 135);
assert_eq!(0.75.scale::<i32>(0, 1, 180, 0), 45);
assert_eq!(100.scale::<i32>(0, 100, 180, 0), 0);
assert_eq!(0.scale::<i64>(100, 0, 180, 0), 0);
assert_eq!(0.75.scale::<i64>(1, 0, 0, 180), 45);
assert_eq!(0.75.scale::<i64>(1, 0, 180, 0), 135);
assert_eq!(100.scale::<i64>(100, 0, 180, 0), 180);
}
#[test]
fn test_scale_float() {
assert!((0.5.scale::<f32>(0, 1, 0, 100) - 50.0).abs() < f32::EPSILON);
assert!((0.scale::<f32>(0, 1, 0, 100) - 0.0).abs() < f32::EPSILON);
assert!((1.scale::<f32>(0, 1, 0, 100) - 100.0).abs() < f32::EPSILON);
assert_eq!(0.scale::<f32>(0, 100, 180, 0), 180.0);
assert_eq!(0.75.scale::<f32>(0, 1, 0, 180), 135.0);
assert_eq!(0.75.scale::<f32>(0, 1, 180, 0), 45.0);
assert_eq!(100.scale::<f32>(0, 100, 180, 0), 0.0);
assert!((0.5.scale::<f64>(0, 1, 0, 100) - 50.0).abs() < f64::EPSILON);
assert!((0.scale::<f64>(0, 1, 0, 100) - 0.0).abs() < f64::EPSILON);
assert!((1.scale::<f64>(0, 1, 0, 100) - 100.0).abs() < f64::EPSILON);
assert_eq!(0.scale::<f64>(0, 100, 180, 0), 180.0);
assert_eq!(0.75.scale::<f64>(0, 1, 0, 180), 135.0);
assert_eq!(0.75.scale::<f64>(0, 1, 180, 0), 45.0);
assert_eq!(100.scale::<f64>(0, 100, 180, 0), 0.0);
}
}