1use crate::dimension::{DimDiv, DimMul, Dimension, Dimensionless};
4use crate::scalar::Scalar;
5use crate::Quantity;
6use core::fmt::{Debug, Display, Formatter, LowerExp, Result, UpperExp};
7use core::marker::PhantomData;
8
9pub trait Unit: Copy + PartialEq + Debug + 'static {
24 const RATIO: f64;
26
27 type Dim: Dimension;
29
30 const SYMBOL: &'static str;
32}
33
34#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
42pub struct Per<N: Unit, D: Unit>(PhantomData<(N, D)>);
43
44impl<N: Unit, D: Unit> Unit for Per<N, D>
45where
46 N::Dim: DimDiv<D::Dim>,
47 <N::Dim as DimDiv<D::Dim>>::Output: Dimension,
48{
49 const RATIO: f64 = N::RATIO / D::RATIO;
50 type Dim = <N::Dim as DimDiv<D::Dim>>::Output;
51 const SYMBOL: &'static str = "";
52}
53
54impl<N: Unit, D: Unit, S: Scalar + Display> Display for Quantity<Per<N, D>, S>
55where
56 N::Dim: DimDiv<D::Dim>,
57 <N::Dim as DimDiv<D::Dim>>::Output: Dimension,
58{
59 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
60 Display::fmt(&self.value(), f)?;
61 write!(f, " {}/{}", N::SYMBOL, D::SYMBOL)
62 }
63}
64
65impl<N: Unit, D: Unit, S: Scalar + LowerExp> LowerExp for Quantity<Per<N, D>, S>
66where
67 N::Dim: DimDiv<D::Dim>,
68 <N::Dim as DimDiv<D::Dim>>::Output: Dimension,
69{
70 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
71 LowerExp::fmt(&self.value(), f)?;
72 write!(f, " {}/{}", N::SYMBOL, D::SYMBOL)
73 }
74}
75
76impl<N: Unit, D: Unit, S: Scalar + UpperExp> UpperExp for Quantity<Per<N, D>, S>
77where
78 N::Dim: DimDiv<D::Dim>,
79 <N::Dim as DimDiv<D::Dim>>::Output: Dimension,
80{
81 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
82 UpperExp::fmt(&self.value(), f)?;
83 write!(f, " {}/{}", N::SYMBOL, D::SYMBOL)
84 }
85}
86
87#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
93pub struct Prod<A: Unit, B: Unit>(PhantomData<(A, B)>);
94
95impl<A: Unit, B: Unit> Unit for Prod<A, B>
96where
97 A::Dim: DimMul<B::Dim>,
98 <A::Dim as DimMul<B::Dim>>::Output: Dimension,
99{
100 const RATIO: f64 = A::RATIO * B::RATIO;
101 type Dim = <A::Dim as DimMul<B::Dim>>::Output;
102 const SYMBOL: &'static str = "";
103}
104
105impl<A: Unit, B: Unit, S: Scalar + Display> Display for Quantity<Prod<A, B>, S>
106where
107 A::Dim: DimMul<B::Dim>,
108 <A::Dim as DimMul<B::Dim>>::Output: Dimension,
109{
110 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
111 Display::fmt(&self.value(), f)?;
112 write!(f, " {}·{}", A::SYMBOL, B::SYMBOL)
113 }
114}
115
116impl<A: Unit, B: Unit, S: Scalar + LowerExp> LowerExp for Quantity<Prod<A, B>, S>
117where
118 A::Dim: DimMul<B::Dim>,
119 <A::Dim as DimMul<B::Dim>>::Output: Dimension,
120{
121 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
122 LowerExp::fmt(&self.value(), f)?;
123 write!(f, " {}·{}", A::SYMBOL, B::SYMBOL)
124 }
125}
126
127impl<A: Unit, B: Unit, S: Scalar + UpperExp> UpperExp for Quantity<Prod<A, B>, S>
128where
129 A::Dim: DimMul<B::Dim>,
130 <A::Dim as DimMul<B::Dim>>::Output: Dimension,
131{
132 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
133 UpperExp::fmt(&self.value(), f)?;
134 write!(f, " {}·{}", A::SYMBOL, B::SYMBOL)
135 }
136}
137
138#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
148pub struct Unitless;
149
150impl Unit for Unitless {
151 const RATIO: f64 = 1.0;
152 type Dim = Dimensionless;
153 const SYMBOL: &'static str = "";
154}
155
156impl<S: Scalar + Display> Display for Quantity<Unitless, S> {
157 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
158 Display::fmt(&self.value(), f)
159 }
160}
161
162impl<S: Scalar + LowerExp> LowerExp for Quantity<Unitless, S> {
163 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
164 LowerExp::fmt(&self.value(), f)
165 }
166}
167
168impl<S: Scalar + UpperExp> UpperExp for Quantity<Unitless, S> {
169 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
170 UpperExp::fmt(&self.value(), f)
171 }
172}
173
174pub trait Simplify {
179 type Scalar: Scalar;
181 type Out: Unit;
183 fn simplify(self) -> Quantity<Self::Out, Self::Scalar>;
185}
186
187impl<U: Unit, S: Scalar> Simplify for Quantity<Per<U, U>, S>
188where
189 U::Dim: DimDiv<U::Dim>,
190 <U::Dim as DimDiv<U::Dim>>::Output: Dimension,
191{
192 type Scalar = S;
193 type Out = Unitless;
194 fn simplify(self) -> Quantity<Unitless, S> {
203 Quantity::new(self.value())
204 }
205}
206
207impl<N: Unit, D: Unit, S: Scalar> Simplify for Quantity<Per<N, Per<N, D>>, S>
208where
209 N::Dim: DimDiv<D::Dim>,
210 <N::Dim as DimDiv<D::Dim>>::Output: Dimension,
211 N::Dim: DimDiv<<N::Dim as DimDiv<D::Dim>>::Output>,
212 <N::Dim as DimDiv<<N::Dim as DimDiv<D::Dim>>::Output>>::Output: Dimension,
213{
214 type Scalar = S;
215 type Out = D;
216 fn simplify(self) -> Quantity<D, S> {
217 Quantity::new(self.value())
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 use crate::units::length::{Kilometer, Meter, Meters};
225 use crate::units::time::{Hour, Second};
226 use crate::Quantity;
227
228 #[test]
231 fn per_display_formats_value_and_symbol() {
232 let qty: Quantity<Per<Meter, Second>> = Quantity::new(10.0);
234 let s = format!("{qty}");
235 assert_eq!(s, "10 m/s");
236 }
237
238 #[test]
239 fn per_display_with_precision() {
240 let qty: Quantity<Per<Meter, Second>> = Quantity::new(1.5);
241 let s = format!("{qty:.2}");
242 assert_eq!(s, "1.50 m/s");
243 }
244
245 #[test]
246 fn per_lower_exp_formats_correctly() {
247 let qty: Quantity<Per<Meter, Second>> = Quantity::new(1000.0);
248 let s = format!("{qty:e}");
249 assert!(s.contains("e"), "Expected scientific notation, got: {s}");
250 assert!(s.ends_with("m/s"), "Expected 'm/s' suffix, got: {s}");
251 }
252
253 #[test]
254 fn per_upper_exp_formats_correctly() {
255 let qty: Quantity<Per<Meter, Second>> = Quantity::new(1000.0);
256 let s = format!("{qty:E}");
257 assert!(s.contains("E"), "Expected uppercase-E notation, got: {s}");
258 assert!(s.ends_with("m/s"), "Expected 'm/s' suffix, got: {s}");
259 }
260
261 #[test]
264 fn prod_display_formats_value_and_symbol() {
265 let qty: Quantity<Prod<Meter, Second>> = Quantity::new(3.0);
266 let s = format!("{qty}");
267 assert_eq!(s, "3 m·s");
268 }
269
270 #[test]
271 fn prod_display_with_precision() {
272 let qty: Quantity<Prod<Meter, Second>> = Quantity::new(2.5);
273 let s = format!("{qty:.3}");
274 assert_eq!(s, "2.500 m·s");
275 }
276
277 #[test]
278 fn prod_lower_exp_formats_correctly() {
279 let qty: Quantity<Prod<Kilometer, Second>> = Quantity::new(5000.0);
280 let s = format!("{qty:.2e}");
281 assert!(s.contains("e"), "Expected scientific notation, got: {s}");
282 assert!(s.ends_with("km·s"), "Expected 'km·s' suffix, got: {s}");
283 }
284
285 #[test]
286 fn prod_upper_exp_formats_correctly() {
287 let qty: Quantity<Prod<Kilometer, Second>> = Quantity::new(5000.0);
288 let s = format!("{qty:.2E}");
289 assert!(s.contains("E"), "Expected uppercase-E notation, got: {s}");
290 assert!(s.ends_with("km·s"), "Expected 'km·s' suffix, got: {s}");
291 }
292
293 #[test]
296 fn unitless_lower_exp_formats_correctly() {
297 let qty: Quantity<Unitless> = Quantity::new(0.5);
298 let s = format!("{qty:e}");
299 assert!(s.contains("e"), "Expected scientific notation, got: {s}");
300 assert!(
302 !s.contains(' '),
303 "Unitless should not have a space, got: {s}"
304 );
305 }
306
307 #[test]
308 fn unitless_upper_exp_formats_correctly() {
309 let qty: Quantity<Unitless> = Quantity::new(0.5);
310 let s = format!("{qty:E}");
311 assert!(s.contains("E"), "Expected uppercase-E notation, got: {s}");
312 }
313
314 #[test]
317 fn simplify_per_u_u_gives_unitless() {
318 let ratio = Meters::new(3.0) / Meters::new(6.0);
319 let unitless: Quantity<Unitless> = ratio.simplify();
320 assert!((unitless.value() - 0.5).abs() < 1e-12);
321 }
322
323 #[test]
326 fn simplify_per_n_per_n_d_gives_d() {
327 let q: Quantity<Per<Meter, Per<Meter, Hour>>> = Quantity::new(42.0);
329 let simplified: Quantity<Hour> = q.simplify();
330 assert!((simplified.value() - 42.0).abs() < 1e-12);
331 }
332}