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}