1#![doc = include_str!("../README.md")]
16
17#[derive(thiserror::Error, Debug, PartialEq)]
19pub enum UnitScaleError {
20 #[error("Outside of type bounds: {0}")]
21 Conversion(String),
22 #[error("Unknown error")]
23 Unknown(String),
24}
25
26pub trait UnitScale {
28 const SCALE: f64;
29}
30
31pub trait Scaled<U> {
33 fn scaled_value(&self) -> U;
34}
35
36#[cfg(test)]
37mod tests {
38 use super::*;
39 use core::marker::PhantomData;
40 use core::mem::discriminant;
41 use float_cmp::approx_eq;
42 use num_traits::{FromPrimitive, ToPrimitive};
43
44 struct Scale0_01;
45
46 impl UnitScale for Scale0_01 {
47 const SCALE: f64 = 0.01;
48 }
49
50 struct Volts<S, U> {
51 value: U,
52 _scale: std::marker::PhantomData<S>,
53 }
54
55 impl<S, U> TryFrom<f64> for Volts<S, U>
56 where
57 S: UnitScale,
58 U: FromPrimitive,
59 {
60 type Error = UnitScaleError;
61 fn try_from(value: f64) -> Result<Self, Self::Error> {
62 let scaled_value = value / S::SCALE;
63
64 if let Some(value) = U::from_f64(scaled_value) {
65 Ok(Self {
66 value,
67 _scale: PhantomData,
68 })
69 } else {
70 Err(UnitScaleError::Conversion(format!(
71 "Scaled {} is outside of {} bounds",
72 scaled_value,
73 std::any::type_name::<U>()
74 )))
75 }
76 }
77 }
78
79 impl<S, U> Scaled<U> for Volts<S, U>
80 where
81 S: UnitScale,
82 U: Copy,
83 {
84 fn scaled_value(&self) -> U {
85 self.value
86 }
87 }
88
89 impl<S, U> Volts<S, U>
90 where
91 S: UnitScale,
92 U: Copy + ToPrimitive,
93 {
94 fn to_f64(&self) -> Option<f64> {
95 self.value.to_f64().map(|v| v * S::SCALE)
96 }
97 }
98
99 #[test]
100 fn data_scaled_to_0_01() {
101 assert_eq!(Scale0_01::SCALE, 0.01);
102 }
103
104 #[test]
105 fn test_scale_0_01_for_u8() {
106 let value: f64 = 1.28;
107 let volts = Volts::<Scale0_01, u8>::try_from(value).unwrap();
108
109 assert_eq!(volts.scaled_value(), 128);
110 assert!(approx_eq!(
111 f64,
112 volts.to_f64().expect("Unable to convert to f64"),
113 value,
114 epsilon = 0.01
115 ));
116 }
117
118 #[test]
119 fn test_scale_0_01_for_u8_out_of_bounds() {
120 let value: f64 = 3.01;
121 let volts = Volts::<Scale0_01, u8>::try_from(value);
122
123 assert!(volts.is_err());
124
125 if let Err(e) = volts {
126 assert_eq!(
127 discriminant(&e),
128 discriminant(&UnitScaleError::Conversion("".into()))
129 );
130 }
131 }
132}