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
36pub trait ScaledPrimitiveByteSize {
37 fn primitive_byte_size() -> usize;
38}
39
40#[cfg(test)]
41mod tests {
42 use super::*;
43 use core::marker::PhantomData;
44 use core::mem::discriminant;
45 use float_cmp::approx_eq;
46 use num_traits::{FromPrimitive, ToPrimitive};
47
48 struct Scale0_01;
49
50 impl UnitScale for Scale0_01 {
51 const SCALE: f64 = 0.01;
52 }
53
54 struct Volts<S, U> {
55 value: U,
56 _scale: std::marker::PhantomData<S>,
57 }
58
59 impl<S, U> TryFrom<f64> for Volts<S, U>
60 where
61 S: UnitScale,
62 U: FromPrimitive,
63 {
64 type Error = UnitScaleError;
65 fn try_from(value: f64) -> Result<Self, Self::Error> {
66 let scaled_value = value / S::SCALE;
67
68 if let Some(value) = U::from_f64(scaled_value) {
69 Ok(Self {
70 value,
71 _scale: PhantomData,
72 })
73 } else {
74 Err(UnitScaleError::Conversion(format!(
75 "Scaled {} is outside of {} bounds",
76 scaled_value,
77 std::any::type_name::<U>()
78 )))
79 }
80 }
81 }
82
83 impl<S, U> Scaled<U> for Volts<S, U>
84 where
85 S: UnitScale,
86 U: Copy,
87 {
88 fn scaled_value(&self) -> U {
89 self.value
90 }
91 }
92
93 impl<S, U> ScaledPrimitiveByteSize for Volts<S, U>
94 where
95 S: UnitScale,
96 U: Copy + ToPrimitive,
97 {
98 fn primitive_byte_size() -> usize {
99 core::mem::size_of::<U>()
100 }
101 }
102
103 impl<S, U> Volts<S, U>
104 where
105 S: UnitScale,
106 U: Copy + ToPrimitive,
107 {
108 fn to_f64(&self) -> Option<f64> {
109 self.value.to_f64().map(|v| v * S::SCALE)
110 }
111 }
112
113 #[test]
114 fn data_scaled_to_0_01() {
115 assert_eq!(Scale0_01::SCALE, 0.01);
116 }
117
118 #[test]
119 fn test_scale_0_01_for_u8() {
120 let value: f64 = 1.28;
121 let volts = Volts::<Scale0_01, u8>::try_from(value).unwrap();
122
123 assert_eq!(volts.scaled_value(), 128);
124 assert!(approx_eq!(
125 f64,
126 volts.to_f64().expect("Unable to convert to f64"),
127 value,
128 epsilon = 0.01
129 ));
130 }
131
132 #[test]
133 fn test_scale_0_01_for_u8_out_of_bounds() {
134 let value: f64 = 3.01;
135 let volts = Volts::<Scale0_01, u8>::try_from(value);
136
137 assert!(volts.is_err());
138
139 if let Err(e) = volts {
140 assert_eq!(
141 discriminant(&e),
142 discriminant(&UnitScaleError::Conversion("".into()))
143 );
144 }
145 }
146
147 #[test]
148 fn primitive_byte_size() {
149 let byte_size = Volts::<Scale0_01, u8>::primitive_byte_size();
150
151 assert_eq!(byte_size, 1, "For `u8` the primitive byte size should be 1");
152 }
153}