hermes_five/utils/
scale.rs

1use std::any::type_name;
2
3pub trait ToF64 {
4    fn to_f64(self) -> f64;
5}
6
7pub trait FromF64 {
8    fn from_f64(value: f64) -> Self;
9}
10
11/// Trait for mapping a value from one scale to another.
12pub trait Scalable {
13    /// Map a value from one scale to another.
14    /// This is equivalent to Arduino map() method:
15    /// <https://www.arduino.cc/reference/en/language/functions/math/map/>
16    ///
17    /// # Parameters
18    /// * `self`:  the value to map
19    /// * `from_low`:  the low end of the originating range
20    /// * `from_high`:  the high end of the originating range
21    /// * `to_low`:  the low end of the target range
22    /// * `to_high`:  the high end of the target range
23    ///
24    /// # Returns
25    /// The mapped value.
26    fn scale<R: FromF64>(
27        self,
28        from_low: impl ToF64,
29        from_high: impl ToF64,
30        to_low: impl ToF64,
31        to_high: impl ToF64,
32    ) -> R;
33}
34
35macro_rules! impl_from_scalable {
36    ($($variant:ty),*) => {
37        $(
38            impl Scalable for $variant {
39                fn scale<R: FromF64>(self, from_low: impl ToF64, from_high: impl ToF64, to_low: impl ToF64, to_high: impl ToF64) -> R {
40                    let from_low = from_low.to_f64();
41                    let from_high = from_high.to_f64();
42                    let to_low = to_low.to_f64();
43                    let to_high = to_high.to_f64();
44
45                    let result = (self as f64 - from_low) * (to_high - to_low) / (from_high - from_low) + to_low;
46
47                    R::from_f64(result)
48                }
49            }
50
51            impl ToF64 for $variant {
52                fn to_f64(self) -> f64 {
53                    self as f64
54                }
55            }
56
57            impl FromF64 for $variant {
58                fn from_f64(value: f64) -> Self {
59                    match type_name::<$variant>() {
60                        "f32" | "f64" => value as $variant,
61                        _ => value.round() as $variant
62                    }
63                }
64            }
65        )*
66    };
67}
68
69// Implement trait for all number types.
70impl_from_scalable!(u8, u16, u32, u64, i8, i16, i32, i64, f32, f64);
71
72#[cfg(test)]
73mod tests {
74    use super::Scalable;
75
76    #[test]
77    fn test_scale_unsigned() {
78        assert_eq!(50.scale::<u8>(0, 100, 0, 255), 128);
79        assert_eq!(0.scale::<u8>(0, 100, 0, 255), 0);
80        assert_eq!(100.scale::<u8>(0, 100, 0, 255), 255);
81
82        assert_eq!(0.scale::<u16>(0, 100, 180, 0), 180);
83        assert_eq!(0.75.scale::<u16>(0, 1, 0, 180), 135);
84        assert_eq!(0.75.scale::<u16>(0, 1, 180, 0), 45);
85        assert_eq!(100.scale::<u16>(0, 100, 180, 0), 0);
86
87        assert_eq!(0.scale::<u32>(0, 100, 180, 0), 180);
88        assert_eq!(0.75.scale::<u32>(0, 1, 0, 180), 135);
89        assert_eq!(0.75.scale::<u32>(0, 1, 180, 0), 45);
90        assert_eq!(100.scale::<u32>(0, 100, 180, 0), 0);
91
92        assert_eq!(0.scale::<u64>(100, 0, 180, 0), 0);
93        assert_eq!(0.75.scale::<u64>(1, 0, 0, 180), 45);
94        assert_eq!(0.75.scale::<u64>(1, 0, 180, 0), 135);
95        assert_eq!(100.scale::<u64>(100, 0, 180, 0), 180);
96    }
97
98    #[test]
99    fn test_scale_signed() {
100        assert_eq!(50.scale::<i8>(0, 100, -50, 50), 0);
101        assert_eq!(0.scale::<i8>(0, 100, -50, 0), -50);
102        assert_eq!(100.scale::<i8>(0, 100, -50, 50), 50);
103
104        assert_eq!(0.scale::<i16>(0, 100, 180, 0), 180);
105        assert_eq!((-0.75).scale::<i16>(0, -1, 0, 180), 135);
106        assert_eq!((-0.25).scale::<i16>(-1, 0, 180, 0), 45);
107        assert_eq!(100.scale::<i16>(0, 100, 180, 0), 0);
108
109        assert_eq!(0.scale::<i32>(0, 100, 180, 0), 180);
110        assert_eq!(0.75.scale::<i32>(0, 1, 0, 180), 135);
111        assert_eq!(0.75.scale::<i32>(0, 1, 180, 0), 45);
112        assert_eq!(100.scale::<i32>(0, 100, 180, 0), 0);
113
114        assert_eq!(0.scale::<i64>(100, 0, 180, 0), 0);
115        assert_eq!(0.75.scale::<i64>(1, 0, 0, 180), 45);
116        assert_eq!(0.75.scale::<i64>(1, 0, 180, 0), 135);
117        assert_eq!(100.scale::<i64>(100, 0, 180, 0), 180);
118    }
119
120    #[test]
121    fn test_scale_float() {
122        assert!((0.5.scale::<f32>(0, 1, 0, 100) - 50.0).abs() < f32::EPSILON);
123        assert!((0.scale::<f32>(0, 1, 0, 100) - 0.0).abs() < f32::EPSILON);
124        assert!((1.scale::<f32>(0, 1, 0, 100) - 100.0).abs() < f32::EPSILON);
125
126        assert_eq!(0.scale::<f32>(0, 100, 180, 0), 180.0);
127        assert_eq!(0.75.scale::<f32>(0, 1, 0, 180), 135.0);
128        assert_eq!(0.75.scale::<f32>(0, 1, 180, 0), 45.0);
129        assert_eq!(100.scale::<f32>(0, 100, 180, 0), 0.0);
130
131        assert!((0.5.scale::<f64>(0, 1, 0, 100) - 50.0).abs() < f64::EPSILON);
132        assert!((0.scale::<f64>(0, 1, 0, 100) - 0.0).abs() < f64::EPSILON);
133        assert!((1.scale::<f64>(0, 1, 0, 100) - 100.0).abs() < f64::EPSILON);
134
135        assert_eq!(0.scale::<f64>(0, 100, 180, 0), 180.0);
136        assert_eq!(0.75.scale::<f64>(0, 1, 0, 180), 135.0);
137        assert_eq!(0.75.scale::<f64>(0, 1, 180, 0), 45.0);
138        assert_eq!(100.scale::<f64>(0, 100, 180, 0), 0.0);
139    }
140}