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 fn $ctor(value: f32) -> Self {
107 Self { value: value * $exp }
108 }
109 pub 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 fn abs(&self) -> Self {
194 Self {
195 value: self.value.abs(),
196 }
197 }
198
199 pub fn floor(&self) -> Self {
200 Self {
201 value: self.value.floor(),
202 }
203 }
204
205 pub fn ceil(&self) -> Self {
206 Self {
207 value: self.value.ceil(),
208 }
209 }
210
211 pub fn round(&self) -> Self {
212 Self {
213 value: self.value.round(),
214 }
215 }
216
217 pub fn trunc(&self) -> Self {
218 Self {
219 value: self.value.trunc(),
220 }
221 }
222
223 pub fn fract(&self) -> Self {
224 Self {
225 value: self.value.fract(),
226 }
227 }
228
229 pub fn is_nan(&self) -> bool {
230 self.value.is_nan()
231 }
232
233 pub fn is_infinite(&self) -> bool {
234 self.value.is_infinite()
235 }
236 }
237
238 qty_ctor!{@impl_arith_ops $typename}
239 qty_format!{$typename => {$($rest)*}}
240
241 impl super::Quantity for $typename {}
242 };
243}
244
245mod current;
246mod energy;
247mod frequency;
248mod percentage;
249mod power;
250mod reactive_power;
251mod voltage;
252
253pub use current::Current;
254pub use energy::Energy;
255pub use frequency::Frequency;
256pub use percentage::Percentage;
257pub use power::Power;
258pub use reactive_power::ReactivePower;
259pub use voltage::Voltage;
260
261#[cfg(test)]
262mod test_utils {
263 #[track_caller]
265 pub(crate) fn assert_f32_eq(a: f32, b: f32) {
266 let epsilon: f32 = 10.0_f32.powf(a.log10().min(b.log10())) * 1e-6;
267 if (a - b).abs() > epsilon {
268 panic!(
269 "assertion failed: `(left ~= right)` (epsilon: {})\n left: `{}`,\n right: `{}`",
270 epsilon, a, b
271 );
272 }
273 }
274}