1pub trait Quantity:
8 std::ops::Add<Output = Self>
9 + std::ops::Sub<Output = Self>
10 + std::ops::Mul<Percentage, Output = Self>
11 + std::ops::Mul<f32, Output = Self>
12 + std::ops::Div<f32, Output = Self>
13 + std::ops::Div<Self, Output = f32>
14 + std::cmp::PartialOrd
15 + std::fmt::Display
16 + Copy
17 + Clone
18 + std::fmt::Debug
19 + Default
20 + Sized
21 + Send
22 + Sync
23{
24 fn zero() -> Self {
25 Self::default()
26 }
27}
28
29impl std::ops::Mul<Percentage> for f32 {
30 type Output = f32;
31
32 fn mul(self, other: Percentage) -> Self::Output {
33 self * other.as_fraction()
34 }
35}
36
37impl Quantity for f32 {}
38
39fn format_float(value: f32, precision: usize) -> String {
41 let mut s = format!("{:.1$}", value, precision);
42 if s.contains('.') {
43 s = s.trim_end_matches('0').to_string();
44 }
45 if s.ends_with('.') {
46 s.pop();
47 }
48 s
49}
50
51macro_rules! qty_format {
52 (@impl $self:ident, $f:ident, $prec:ident,
53 ($ctor:ident, $getter:ident, $unit:literal, $exp:literal),
54 ) => {
55 write!($f, "{} {}", format_float( $self.$getter(), $prec), $unit)
56 };
57
58 (@impl $self:ident, $f:ident, $prec:ident,
59 ($ctor1:ident, $getter1:ident, $unit1:literal, $exp1:literal),
60 ($ctor2:ident, $getter2:ident, $unit2:literal, $exp2:literal), $($rest:tt)*
61 ) => {{
62 const {assert!($exp1 < $exp2, "Units must be in increasing order of magnitude.")};
63
64 if $exp1 <= $self.value.abs() && $self.value.abs() < $exp2 {
65 write!($f, "{} {}", format_float( $self.$getter1(), $prec), $unit1)
66 } else {
67 qty_format!(@impl $self, $f, $prec, ($ctor2, $getter2, $unit2, $exp2), $($rest)*)
68 }}
69 };
70
71 (@impl $self:ident, $f:ident, $prec:ident,
72 ($ctor1:ident, $getter1:ident, $unit1:literal, $exp1:literal),
73 ($ctor2:ident, $getter2:ident, None, $exp2:literal),
74 ) => {
75 write!($f, "{} {}", format_float( $self.$getter1(), $prec), $unit1)
76 };
77
78 (@start $self:ident, $f:ident, $prec:ident,
79 ($ctor:ident, $getter:ident, $unit:literal, $exp:literal), $($rest:tt)*
80 ) => {
81 if $self.value.abs() <= $exp {
82 write!($f, "{} {}", format_float( $self.$getter(), $prec), $unit)
83 } else {
84 qty_format!(@impl $self, $f, $prec, ($ctor, $getter, $unit, $exp), $($rest)*)
85 }
86 };
87
88 ($typename:ident => {$($rest:tt)*}) => {
89 use super::format_float;
90 impl std::fmt::Display for $typename {
91 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
92 let prec = if let Some(prec) = f.precision() {
93 prec
94 } else {
95 3
96 };
97 qty_format!(@start self, f, prec, $($rest)*)
98 }
99
100 }
101 };
102}
103
104macro_rules! qty_ctor {
105 (@impl ($ctor:ident, $getter:ident, $unit:tt, $exp:literal) $(,)?) => {
106 pub const fn $ctor(value: f32) -> Self {
107 Self { value: value * $exp }
108 }
109 pub const fn $getter(&self) -> f32 {
110 self.value / $exp
111 }
112 };
113 (@impl ($ctor:ident, $getter:ident, $unit:tt, $exp:literal), $($rest:tt)*) => {
114 qty_ctor!(@impl ($ctor, $getter, $unit, $exp));
115 qty_ctor!(@impl $($rest)*);
116 };
117 (@impl_arith_ops $typename:ident) => {
118 impl std::ops::Add for $typename {
119 type Output = Self;
120
121 fn add(self, rhs: Self) -> Self::Output {
122 Self {
123 value: self.value + rhs.value,
124 }
125 }
126 }
127
128 impl std::ops::Sub for $typename {
129 type Output = Self;
130
131 fn sub(self, rhs: Self) -> Self::Output {
132 Self {
133 value: self.value - rhs.value,
134 }
135 }
136 }
137
138 impl std::ops::Mul<super::Percentage> for $typename {
139 type Output = Self;
140
141 fn mul(self, other: super::Percentage) -> Self::Output {
142 Self {
143 value: self.value * other.as_fraction(),
144 }
145 }
146 }
147
148 impl std::ops::Mul<f32> for $typename {
149 type Output = Self;
150
151 fn mul(self, other: f32) -> Self::Output {
152 Self {
153 value: self.value * other,
154 }
155 }
156 }
157
158 impl std::ops::Div<f32> for $typename {
159 type Output = Self;
160
161 fn div(self, other: f32) -> Self::Output {
162 Self {
163 value: self.value / other,
164 }
165 }
166 }
167
168 impl std::ops::Div<$typename> for $typename {
169 type Output = f32;
170
171 fn div(self, other: Self) -> Self::Output {
172 self.value / other.value
173 }
174 }
175
176 impl std::cmp::PartialOrd for $typename {
177 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
178 self.value.partial_cmp(&other.value)
179 }
180 }
181
182 };
183 (#[$meta:meta] $typename:ident => {$($rest:tt)*}) => {
184 #[$meta]
185 #[derive(Copy, Clone, Debug, Default, PartialEq)]
186 pub struct $typename {
187 value: f32,
188 }
189
190 impl $typename {
191 qty_ctor!(@impl $($rest)*);
192
193 pub const fn abs(&self) -> Self {
194 Self {
195 value: self.value.abs(),
196 }
197 }
198
199 pub const fn floor(&self) -> Self {
200 Self {
201 value: self.value.floor(),
202 }
203 }
204
205 pub const fn ceil(&self) -> Self {
206 Self {
207 value: self.value.ceil(),
208 }
209 }
210
211 pub const fn round(&self) -> Self {
212 Self {
213 value: self.value.round(),
214 }
215 }
216
217 pub const fn trunc(&self) -> Self {
218 Self {
219 value: self.value.trunc(),
220 }
221 }
222
223 pub const fn fract(&self) -> Self {
224 Self {
225 value: self.value.fract(),
226 }
227 }
228
229 pub const fn is_nan(&self) -> bool {
230 self.value.is_nan()
231 }
232
233 pub const fn is_infinite(&self) -> bool {
234 self.value.is_infinite()
235 }
236
237 pub const fn min(self, other: Self) -> Self {
238 Self {
239 value: self.value.min(other.value),
240 }
241 }
242
243 pub const fn max(self, other: Self) -> Self {
244 Self {
245 value: self.value.max(other.value),
246 }
247 }
248 }
249
250 qty_ctor!{@impl_arith_ops $typename}
251 qty_format!{$typename => {$($rest)*}}
252
253 impl super::Quantity for $typename {}
254 };
255}
256
257mod current;
258mod energy;
259mod frequency;
260mod percentage;
261mod power;
262mod reactive_power;
263mod voltage;
264
265pub use current::Current;
266pub use energy::Energy;
267pub use frequency::Frequency;
268pub use percentage::Percentage;
269pub use power::Power;
270pub use reactive_power::ReactivePower;
271pub use voltage::Voltage;
272
273#[cfg(test)]
274mod test_utils {
275 #[track_caller]
277 pub(crate) fn assert_f32_eq(a: f32, b: f32) {
278 let epsilon: f32 = 10.0_f32.powf(a.log10().min(b.log10())) * 1e-6;
279 if (a - b).abs() > epsilon {
280 panic!(
281 "assertion failed: `(left ~= right)` (epsilon: {})\n left: `{}`,\n right: `{}`",
282 epsilon, a, b
283 );
284 }
285 }
286}