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
11pub trait Scalable {
13 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
69impl_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}