Skip to main content

ucum/
dimension.rs

1//! The dimensional exponent vector.
2
3use core::fmt;
4
5/// Number of UCUM base quantities.
6pub(crate) const NDIM: usize = 7;
7
8/// Canonical UCUM base units, in vector order, used by [`Dimension::Display`].
9pub(crate) const BASE_SYMBOLS: [&str; NDIM] = ["m", "s", "g", "rad", "K", "C", "cd"];
10
11/// A dimensional exponent vector over the seven UCUM base quantities.
12///
13/// The exponent order is fixed and documented:
14///
15/// | index | quantity            | base unit |
16/// |-------|---------------------|-----------|
17/// | 0     | length              | `m`       |
18/// | 1     | time                | `s`       |
19/// | 2     | mass                | `g`       |
20/// | 3     | plane angle         | `rad`     |
21/// | 4     | temperature         | `K`       |
22/// | 5     | electric charge     | `C`       |
23/// | 6     | luminous intensity  | `cd`      |
24///
25/// All arithmetic saturates at the `i8` bounds rather than overflowing, so the
26/// type can never panic, even on absurd inputs such as `m120.m120`.
27///
28/// ```
29/// use ucum::Dimension;
30/// let area = Dimension::DIMENSIONLESS.mul(Dimension([2, 0, 0, 0, 0, 0, 0]));
31/// assert_eq!(area, Dimension([2, 0, 0, 0, 0, 0, 0]));
32/// ```
33#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
34pub struct Dimension(pub [i8; NDIM]);
35
36impl Dimension {
37    /// The dimensionless dimension (all exponents zero).
38    pub const DIMENSIONLESS: Dimension = Dimension([0; NDIM]);
39
40    /// Returns `true` when every exponent is zero.
41    #[must_use]
42    pub fn is_dimensionless(&self) -> bool {
43        self.0.iter().all(|&e| e == 0)
44    }
45
46    /// Multiplies two dimensions by adding their exponents (saturating).
47    ///
48    /// Named `mul` per the public UCUM API contract; it intentionally does not
49    /// implement [`std::ops::Mul`], to keep the type's surface explicit.
50    #[must_use]
51    #[allow(clippy::should_implement_trait)]
52    pub fn mul(self, other: Dimension) -> Dimension {
53        let mut out = self.0;
54        for (o, &b) in out.iter_mut().zip(other.0.iter()) {
55            *o = o.saturating_add(b);
56        }
57        Dimension(out)
58    }
59
60    /// Inverts a dimension by negating its exponents (saturating).
61    #[must_use]
62    pub fn inv(self) -> Dimension {
63        let mut out = self.0;
64        for e in &mut out {
65            *e = e.saturating_neg();
66        }
67        Dimension(out)
68    }
69
70    /// Raises a dimension to an integer power (saturating).
71    #[must_use]
72    pub fn powi(self, n: i8) -> Dimension {
73        let mut out = self.0;
74        for e in &mut out {
75            *e = e.saturating_mul(n);
76        }
77        Dimension(out)
78    }
79}
80
81impl fmt::Display for Dimension {
82    /// Renders a dimension in UCUM base-unit syntax, e.g. `m3.s-1`.
83    ///
84    /// The dimensionless dimension renders as `1`.
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        if self.is_dimensionless() {
87            return f.write_str("1");
88        }
89        let mut first = true;
90        for (i, &e) in self.0.iter().enumerate() {
91            if e == 0 {
92                continue;
93            }
94            if !first {
95                f.write_str(".")?;
96            }
97            first = false;
98            if e == 1 {
99                write!(f, "{}", BASE_SYMBOLS[i])?;
100            } else {
101                write!(f, "{}{}", BASE_SYMBOLS[i], e)?;
102            }
103        }
104        Ok(())
105    }
106}