ucum/
dimension.rs

1//! Representation of the dimension of units and quantities.
2//!
3//! See http://unitsofmeasure.org/ucum.html#section-Semantics
4
5use super::*;
6use std::fmt;
7use std::ops::Index;
8
9/// An enum for definindex of base dimensions.
10pub enum BaseDimension {
11    /// The index of the dimension of the base unit 'meter'
12    Length = 0,
13    /// The index of the dimension of the base base unit 'second'
14    Time = 1,
15    /// The index of the dimension of the base unit 'gram'
16    Mass = 2,
17    /// The index of the dimension of the base unit 'radian'
18    PlaneAngle = 3,
19    /// The index of the dimension of the base unit 'Kelvin'
20    Temperature = 4,
21    /// The index of the dimension of the base unit 'Coulomb'
22    ElectricCharge = 5,
23    /// The index of the dimension of the base unit 'candela'
24    LuminousIntensity = 6,
25}
26
27const DIMENSION_SIZE: usize = 7;
28const SYMBOL: [&str; DIMENSION_SIZE] = ["m", "s", "g", "rad", "K", "C", "cd"];
29
30/// The dimension of a unit or quantity.
31#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
32pub enum Dimension {
33    /// The dimension of standard units.
34    Standard([i8; DIMENSION_SIZE]),
35    // TODO actually arbitrary units can be complex, so this should rather be a hashmap<str, i8>?
36    /// The dimension of [arbitrary units](http://unitsofmeasure.org/ucum.html#section-Arbitrary-Units).
37    Arbitrary(&'static str),
38}
39
40use Dimension::*;
41
42impl Dimension {
43    /// The dimension of dimension-less quantities.
44    ///
45    /// See also the [`base`](./base/index.html) module.
46    #[inline]
47    pub const fn less() -> Dimension {
48        Standard([0, 0, 0, 0, 0, 0, 0])
49    }
50
51    /// The dimension of lengths.
52    ///
53    /// See also the [`base`](./base/index.html) module.
54    #[inline]
55    pub const fn meter() -> Dimension {
56        Standard([1, 0, 0, 0, 0, 0, 0])
57    }
58
59    /// The dimension of times.
60    ///
61    /// See also the [`base`](./base/index.html) module.
62    #[inline]
63    pub const fn second() -> Dimension {
64        Standard([0, 1, 0, 0, 0, 0, 0])
65    }
66
67    /// The dimension of weights.
68    ///
69    /// See also the [`base`](./base/index.html) module.
70    #[inline]
71    pub const fn gram() -> Dimension {
72        Standard([0, 0, 1, 0, 0, 0, 0])
73    }
74
75    /// The dimension of planar angles.
76    ///
77    /// See also the [`base`](./base/index.html) module.
78    #[inline]
79    pub const fn radian() -> Dimension {
80        Standard([0, 0, 0, 1, 0, 0, 0])
81    }
82
83    /// The dimension of temperatures.
84    ///
85    /// See also the [`base`](./base/index.html) module.
86    #[inline]
87    pub const fn kelvin() -> Dimension {
88        Standard([0, 0, 0, 0, 1, 0, 0])
89    }
90
91    /// The dimension of electric charges.
92    ///
93    /// See also the [`base`](./base/index.html) module.
94    #[inline]
95    pub const fn coulomb() -> Dimension {
96        Standard([0, 0, 0, 0, 0, 1, 0])
97    }
98
99    /// The dimension of luminous intensities.
100    ///
101    /// See also the [`base`](./base/index.html) module.
102    #[inline]
103    pub const fn candela() -> Dimension {
104        Standard([0, 0, 0, 0, 0, 0, 1])
105    }
106
107    /// An arbitrary dimension (see http://unitsofmeasure.org/ucum.html#section-Arbitrary-Units).
108    #[inline]
109    pub const fn arbitrary(name: &'static str) -> Dimension {
110        Arbitrary(name)
111    }
112
113    /// Whether this dimension is [arbitrary](http://unitsofmeasure.org/ucum.html#section-Arbitrary-Units).
114    #[inline]
115    pub fn is_arbitrary(&self) -> bool {
116        matches!(self, Arbitrary(_))
117    }
118
119    /// Raise this dimension to the given power.
120    #[inline]
121    pub fn raise(&mut self, power: i8) {
122        match self {
123            Standard(a) => {
124                for ai in a.iter_mut() {
125                    *ai *= power;
126                }
127            }
128            Arbitrary(_) => unimplemented!(),
129        }
130    }
131
132    /// Return a copy of this dimension raised to the given power.
133    #[inline]
134    pub fn raised(self, power: i8) -> Dimension {
135        let mut r = self;
136        r.raise(power);
137        r
138    }
139
140    /// Invert this dimension.
141    pub fn invert(&mut self) {
142        self.raise(-1)
143    }
144
145    /// Return an inverted copy of this dimension.
146    #[inline]
147    pub fn inverted(&self) -> Dimension {
148        self.raised(-1)
149    }
150}
151
152impl Default for Dimension {
153    fn default() -> Dimension {
154        Dimension::less()
155    }
156}
157
158impl Index<BaseDimension> for Dimension {
159    type Output = i8;
160    fn index(&self, i: BaseDimension) -> &i8 {
161        match self {
162            Standard(a) => &a[i as usize],
163            Arbitrary(_) => &0,
164        }
165    }
166}
167
168/// # Panic
169/// Both self and rhs must have a standard dimension, otherwise this operation will panic.
170impl Mul<Dimension> for Dimension {
171    type Output = Dimension;
172
173    #[allow(clippy::suspicious_arithmetic_impl)]
174    fn mul(self, other: Dimension) -> Self::Output {
175        match (self, other) {
176            (Standard(mut a1), Standard(a2)) => {
177                for i in 0..DIMENSION_SIZE {
178                    a1[i] += a2[i];
179                }
180                Standard(a1)
181            }
182            (Arbitrary(_), _) => panic!("Can not multiply: LHS is arbitrary"),
183            (_, Arbitrary(_)) => panic!("Can not multiply: RHS is arbitrary"),
184        }
185    }
186}
187
188/// # Panic
189/// Both self and rhs must have a standard dimension, otherwise this operation will panic.
190impl Div<Dimension> for Dimension {
191    type Output = Dimension;
192
193    #[allow(clippy::suspicious_arithmetic_impl)]
194    fn div(self, other: Dimension) -> Self::Output {
195        match (self, other) {
196            (Standard(mut a1), Standard(a2)) => {
197                for i in 0..DIMENSION_SIZE {
198                    a1[i] -= a2[i];
199                }
200                Standard(a1)
201            }
202            (Arbitrary(_), _) => panic!("Can not multiply: LHS is arbitrary"),
203            (_, Arbitrary(_)) => panic!("Can not multiply: RHS is arbitrary"),
204        }
205    }
206}
207
208impl Mul<Dimension> for f64 {
209    type Output = Quantity<f64>;
210
211    fn mul(self, rhs: Dimension) -> Quantity<f64> {
212        Quantity::new(self, rhs)
213    }
214}
215
216impl Mul<Dimension> for i32 {
217    type Output = Quantity<f64>;
218
219    fn mul(self, rhs: Dimension) -> Quantity<f64> {
220        Quantity::new(self as f64, rhs)
221    }
222}
223
224impl fmt::Display for Dimension {
225    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
226        match self {
227            Standard(a) => {
228                let mut first = true;
229                for i in 0..DIMENSION_SIZE {
230                    if a[i] == 0 {
231                        continue;
232                    }
233                    if first {
234                        first = false;
235                    } else {
236                        write!(f, ".")?;
237                    }
238                    write!(f, "{}", SYMBOL[i])?;
239                    if a[i] == 1 {
240                        continue;
241                    }
242                    let abs = if a[i] < 0 {
243                        write!(f, "⁻")?;
244                        -a[i]
245                    } else {
246                        a[i]
247                    };
248                    match abs {
249                        1 => write!(f, "¹")?,
250                        2 => write!(f, "²")?,
251                        3 => write!(f, "³")?,
252                        4 => write!(f, "⁴")?,
253                        _ => write!(f, "{}", abs)?,
254                    }
255                }
256                Ok(())
257            }
258            Arbitrary(name) => write!(f, "{}", name),
259        }
260    }
261}
262
263/// Static symbols for base dimensions.
264pub mod base {
265    use super::Dimension;
266    /// The dimension of dimension-less quantities.
267    pub static DIMLESS: Dimension = Dimension::less();
268    /// The dimension of lengths.
269    pub static M: Dimension = Dimension::meter();
270    /// The dimension of times.
271    pub static S: Dimension = Dimension::second();
272    /// The dimension of weights.
273    pub static G: Dimension = Dimension::gram();
274    /// The dimension of planar angles.
275    pub static RAD: Dimension = Dimension::radian();
276    /// The dimension of temperatures.
277    pub static K: Dimension = Dimension::kelvin();
278    /// The dimension of electric charges.
279    pub static C: Dimension = Dimension::coulomb();
280    /// The dimension of luminous intensities.
281    pub static CD: Dimension = Dimension::candela();
282}
283
284#[cfg(test)]
285mod test {
286    use super::*;
287    use base::*;
288
289    #[test]
290    fn test_dimless() {
291        let u = Dimension::less();
292        assert_eq!(u[BaseDimension::Length], 0);
293        assert_eq!(u[BaseDimension::Time], 0);
294        assert_eq!(u[BaseDimension::Mass], 0);
295        assert_eq!(u[BaseDimension::PlaneAngle], 0);
296        assert_eq!(u[BaseDimension::Temperature], 0);
297        assert_eq!(u[BaseDimension::ElectricCharge], 0);
298        assert_eq!(u[BaseDimension::LuminousIntensity], 0);
299        assert_eq!(base::DIMLESS, u);
300        assert_eq!(&format!("{}", &u), "");
301    }
302
303    #[test]
304    fn test_meter() {
305        let m = Dimension::meter();
306        assert_eq!(m[BaseDimension::Length], 1);
307        assert_eq!(m[BaseDimension::Time], 0);
308        assert_eq!(m[BaseDimension::Mass], 0);
309        assert_eq!(m[BaseDimension::PlaneAngle], 0);
310        assert_eq!(m[BaseDimension::Temperature], 0);
311        assert_eq!(m[BaseDimension::ElectricCharge], 0);
312        assert_eq!(m[BaseDimension::LuminousIntensity], 0);
313        assert_eq!(base::M, m);
314        assert_eq!(&format!("{}", &m), "m");
315    }
316
317    #[test]
318    fn test_second() {
319        let s = Dimension::second();
320        assert_eq!(s[BaseDimension::Length], 0);
321        assert_eq!(s[BaseDimension::Time], 1);
322        assert_eq!(s[BaseDimension::Mass], 0);
323        assert_eq!(s[BaseDimension::PlaneAngle], 0);
324        assert_eq!(s[BaseDimension::Temperature], 0);
325        assert_eq!(s[BaseDimension::ElectricCharge], 0);
326        assert_eq!(s[BaseDimension::LuminousIntensity], 0);
327        assert_eq!(base::S, s);
328        assert_eq!(&format!("{}", &s), "s");
329    }
330
331    #[test]
332    fn test_gram() {
333        let g = Dimension::gram();
334        assert_eq!(g[BaseDimension::Length], 0);
335        assert_eq!(g[BaseDimension::Time], 0);
336        assert_eq!(g[BaseDimension::Mass], 1);
337        assert_eq!(g[BaseDimension::PlaneAngle], 0);
338        assert_eq!(g[BaseDimension::Temperature], 0);
339        assert_eq!(g[BaseDimension::ElectricCharge], 0);
340        assert_eq!(g[BaseDimension::LuminousIntensity], 0);
341        assert_eq!(base::G, g);
342        assert_eq!(&format!("{}", &g), "g");
343    }
344
345    #[test]
346    fn test_radian() {
347        let rad = Dimension::radian();
348        assert_eq!(rad[BaseDimension::Length], 0);
349        assert_eq!(rad[BaseDimension::Time], 0);
350        assert_eq!(rad[BaseDimension::Mass], 0);
351        assert_eq!(rad[BaseDimension::PlaneAngle], 1);
352        assert_eq!(rad[BaseDimension::Temperature], 0);
353        assert_eq!(rad[BaseDimension::ElectricCharge], 0);
354        assert_eq!(rad[BaseDimension::LuminousIntensity], 0);
355        assert_eq!(base::RAD, rad);
356        assert_eq!(&format!("{}", &rad), "rad");
357    }
358
359    #[test]
360    fn test_kelvin() {
361        let k = Dimension::kelvin();
362        assert_eq!(k[BaseDimension::Length], 0);
363        assert_eq!(k[BaseDimension::Time], 0);
364        assert_eq!(k[BaseDimension::Mass], 0);
365        assert_eq!(k[BaseDimension::PlaneAngle], 0);
366        assert_eq!(k[BaseDimension::Temperature], 1);
367        assert_eq!(k[BaseDimension::ElectricCharge], 0);
368        assert_eq!(k[BaseDimension::LuminousIntensity], 0);
369        assert_eq!(base::K, k);
370        assert_eq!(&format!("{}", &k), "K");
371    }
372
373    #[test]
374    fn test_coulomb() {
375        let c = Dimension::coulomb();
376        assert_eq!(c[BaseDimension::Length], 0);
377        assert_eq!(c[BaseDimension::Time], 0);
378        assert_eq!(c[BaseDimension::Mass], 0);
379        assert_eq!(c[BaseDimension::PlaneAngle], 0);
380        assert_eq!(c[BaseDimension::Temperature], 0);
381        assert_eq!(c[BaseDimension::ElectricCharge], 1);
382        assert_eq!(c[BaseDimension::LuminousIntensity], 0);
383        assert_eq!(base::C, c);
384        assert_eq!(&format!("{}", &c), "C");
385    }
386
387    #[test]
388    fn test_candela() {
389        let cd = Dimension::candela();
390        assert_eq!(cd[BaseDimension::Length], 0);
391        assert_eq!(cd[BaseDimension::Time], 0);
392        assert_eq!(cd[BaseDimension::Mass], 0);
393        assert_eq!(cd[BaseDimension::PlaneAngle], 0);
394        assert_eq!(cd[BaseDimension::Temperature], 0);
395        assert_eq!(cd[BaseDimension::ElectricCharge], 0);
396        assert_eq!(cd[BaseDimension::LuminousIntensity], 1);
397        assert_eq!(base::CD, cd);
398        assert_eq!(&format!("{}", &cd), "cd");
399    }
400
401    #[test]
402    fn test_m2() {
403        let m2 = M * M;
404        assert_eq!(m2[BaseDimension::Length], 2);
405        assert_eq!(m2[BaseDimension::Time], 0);
406        assert_eq!(m2[BaseDimension::Mass], 0);
407        assert_eq!(m2[BaseDimension::PlaneAngle], 0);
408        assert_eq!(m2[BaseDimension::Temperature], 0);
409        assert_eq!(m2[BaseDimension::ElectricCharge], 0);
410        assert_eq!(m2[BaseDimension::LuminousIntensity], 0);
411        assert_eq!(&format!("{}", &m2), "m²");
412    }
413
414    #[test]
415    fn test_ms() {
416        let ms = M * S;
417        assert_eq!(ms[BaseDimension::Length], 1);
418        assert_eq!(ms[BaseDimension::Time], 1);
419        assert_eq!(ms[BaseDimension::Mass], 0);
420        assert_eq!(ms[BaseDimension::PlaneAngle], 0);
421        assert_eq!(ms[BaseDimension::Temperature], 0);
422        assert_eq!(ms[BaseDimension::ElectricCharge], 0);
423        assert_eq!(ms[BaseDimension::LuminousIntensity], 0);
424        assert_eq!(&format!("{}", &ms), "m.s");
425    }
426
427    #[test]
428    fn test_mps() {
429        let mps = M / S;
430        assert_eq!(mps[BaseDimension::Length], 1);
431        assert_eq!(mps[BaseDimension::Time], -1);
432        assert_eq!(mps[BaseDimension::Mass], 0);
433        assert_eq!(mps[BaseDimension::PlaneAngle], 0);
434        assert_eq!(mps[BaseDimension::Temperature], 0);
435        assert_eq!(mps[BaseDimension::ElectricCharge], 0);
436        assert_eq!(mps[BaseDimension::LuminousIntensity], 0);
437        assert_eq!(&format!("{}", &mps), "m.s⁻¹");
438    }
439
440    #[test]
441    fn test_mps2() {
442        let mps2 = M / (S * S);
443        assert_eq!(mps2[BaseDimension::Length], 1);
444        assert_eq!(mps2[BaseDimension::Time], -2);
445        assert_eq!(mps2[BaseDimension::Mass], 0);
446        assert_eq!(mps2[BaseDimension::PlaneAngle], 0);
447        assert_eq!(mps2[BaseDimension::Temperature], 0);
448        assert_eq!(mps2[BaseDimension::ElectricCharge], 0);
449        assert_eq!(mps2[BaseDimension::LuminousIntensity], 0);
450        assert_eq!(&format!("{}", &mps2), "m.s⁻²");
451    }
452
453    #[test]
454    fn test_inverted() {
455        let mm1 = M.inverted();
456        assert_eq!(mm1[BaseDimension::Length], -1);
457        assert_eq!(mm1[BaseDimension::Time], 0);
458        assert_eq!(mm1[BaseDimension::Mass], 0);
459        assert_eq!(mm1[BaseDimension::PlaneAngle], 0);
460        assert_eq!(mm1[BaseDimension::Temperature], 0);
461        assert_eq!(mm1[BaseDimension::ElectricCharge], 0);
462        assert_eq!(mm1[BaseDimension::LuminousIntensity], 0);
463        assert_eq!(&format!("{}", &mm1), "m⁻¹");
464
465        let d = (M * M / S).inverted();
466        assert_eq!(d[BaseDimension::Length], -2);
467        assert_eq!(d[BaseDimension::Time], 1);
468        assert_eq!(d[BaseDimension::Mass], 0);
469        assert_eq!(d[BaseDimension::PlaneAngle], 0);
470        assert_eq!(d[BaseDimension::Temperature], 0);
471        assert_eq!(d[BaseDimension::ElectricCharge], 0);
472        assert_eq!(d[BaseDimension::LuminousIntensity], 0);
473        assert_eq!(&format!("{}", &d), "m⁻².s");
474    }
475
476    #[test]
477    fn test_invert() {
478        let mut d = M;
479        d.invert();
480        assert_eq!(d[BaseDimension::Length], -1);
481        assert_eq!(d[BaseDimension::Time], 0);
482        assert_eq!(d[BaseDimension::Mass], 0);
483        assert_eq!(d[BaseDimension::PlaneAngle], 0);
484        assert_eq!(d[BaseDimension::Temperature], 0);
485        assert_eq!(d[BaseDimension::ElectricCharge], 0);
486        assert_eq!(d[BaseDimension::LuminousIntensity], 0);
487        assert_eq!(&format!("{}", &d), "m⁻¹");
488
489        let mut d = M * M / S;
490        d.invert();
491        assert_eq!(d[BaseDimension::Length], -2);
492        assert_eq!(d[BaseDimension::Time], 1);
493        assert_eq!(d[BaseDimension::Mass], 0);
494        assert_eq!(d[BaseDimension::PlaneAngle], 0);
495        assert_eq!(d[BaseDimension::Temperature], 0);
496        assert_eq!(d[BaseDimension::ElectricCharge], 0);
497        assert_eq!(d[BaseDimension::LuminousIntensity], 0);
498        assert_eq!(&format!("{}", &d), "m⁻².s");
499    }
500}