Skip to main content

qtty_core/
unit.rs

1//! Unit types and traits.
2
3use crate::dimension::{DimDiv, DimMul, Dimension, Dimensionless};
4use crate::scalar::Scalar;
5use crate::Quantity;
6use core::fmt::{Debug, Display, Formatter, Result};
7use core::marker::PhantomData;
8
9/// Trait implemented by every **unit** type.
10///
11/// * `RATIO` is the conversion factor from this unit to the *canonical scaling unit* of the same dimension.
12///   Example: if metres are canonical (`Meter::RATIO == 1.0`), then kilometres use `Kilometer::RATIO == 1000.0`
13///   because `1 km = 1000 m`.
14///
15/// * `SYMBOL` is the printable string (e.g. `"m"` or `"km"`).
16///
17/// * `Dim` ties the unit to its underlying [`Dimension`].
18///
19/// # Invariants
20///
21/// - Implementations should be zero-sized marker types (this crate's built-in units are unit structs with no fields).
22/// - `RATIO` should be finite and non-zero.
23pub trait Unit: Copy + PartialEq + Debug + 'static {
24    /// Unit-to-canonical conversion factor.
25    const RATIO: f64;
26
27    /// Dimension to which this unit belongs.
28    type Dim: Dimension;
29
30    /// Printable symbol, shown by [`core::fmt::Display`].
31    const SYMBOL: &'static str;
32}
33
34/// Unit representing the division of two other units.
35///
36/// `Per<N, D>` corresponds to `N / D` and carries both the
37/// dimensional information and the scaling ratio between the
38/// constituent units. It is generic over any numerator and
39/// denominator units, which allows implementing arithmetic
40/// generically for all pairs without bespoke macros.
41#[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        write!(f, "{} {}/{}", self.value(), N::SYMBOL, D::SYMBOL)
61    }
62}
63
64/// Unit representing the product of two other units.
65///
66/// `Prod<A, B>` corresponds to `A * B` and carries both the
67/// dimensional information and the scaling ratio between the
68/// constituent units.
69#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
70pub struct Prod<A: Unit, B: Unit>(PhantomData<(A, B)>);
71
72impl<A: Unit, B: Unit> Unit for Prod<A, B>
73where
74    A::Dim: DimMul<B::Dim>,
75    <A::Dim as DimMul<B::Dim>>::Output: Dimension,
76{
77    const RATIO: f64 = A::RATIO * B::RATIO;
78    type Dim = <A::Dim as DimMul<B::Dim>>::Output;
79    const SYMBOL: &'static str = "";
80}
81
82impl<A: Unit, B: Unit, S: Scalar + Display> Display for Quantity<Prod<A, B>, S>
83where
84    A::Dim: DimMul<B::Dim>,
85    <A::Dim as DimMul<B::Dim>>::Output: Dimension,
86{
87    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
88        write!(f, "{} {}ยท{}", self.value(), A::SYMBOL, B::SYMBOL)
89    }
90}
91
92/// Zero-sized marker type for dimensionless quantities.
93///
94/// `Unitless` represents a dimensionless unit with a conversion ratio of 1.0
95/// and an empty symbol. It is used to model the result of simplifying same-unit
96/// ratios (e.g., `Meters / Meters`) into a plain "number-like" `Quantity<Unitless>`.
97///
98/// Unlike a type alias to `f64`, this is a proper zero-sized type, which ensures
99/// that only explicitly constructed `Quantity<Unitless>` values are treated as
100/// dimensionless, not bare `f64` primitives.
101#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
102pub struct Unitless;
103
104impl Unit for Unitless {
105    const RATIO: f64 = 1.0;
106    type Dim = Dimensionless;
107    const SYMBOL: &'static str = "";
108}
109
110impl<S: Scalar + Display> Display for Quantity<Unitless, S> {
111    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
112        write!(f, "{}", self.value())
113    }
114}
115
116/// Trait for simplifying composite unit types.
117///
118/// This allows reducing complex unit expressions to simpler forms,
119/// such as `Per<U, U>` to `Unitless` or `Per<N, Per<N, D>>` to `D`.
120pub trait Simplify {
121    /// The scalar type of this quantity.
122    type Scalar: Scalar;
123    /// The simplified unit type.
124    type Out: Unit;
125    /// Convert this quantity to its simplified unit.
126    fn simplify(self) -> Quantity<Self::Out, Self::Scalar>;
127}
128
129impl<U: Unit, S: Scalar> Simplify for Quantity<Per<U, U>, S>
130where
131    U::Dim: DimDiv<U::Dim>,
132    <U::Dim as DimDiv<U::Dim>>::Output: Dimension,
133{
134    type Scalar = S;
135    type Out = Unitless;
136    /// ```rust
137    /// use qtty_core::length::Meters;
138    /// use qtty_core::{Quantity, Simplify, Unitless};
139    ///
140    /// let ratio = Meters::new(1.0) / Meters::new(2.0);
141    /// let unitless: Quantity<Unitless> = ratio.simplify();
142    /// assert!((unitless.value() - 0.5).abs() < 1e-12);
143    /// ```
144    fn simplify(self) -> Quantity<Unitless, S> {
145        Quantity::new(self.value())
146    }
147}
148
149impl<N: Unit, D: Unit, S: Scalar> Simplify for Quantity<Per<N, Per<N, D>>, S>
150where
151    N::Dim: DimDiv<D::Dim>,
152    <N::Dim as DimDiv<D::Dim>>::Output: Dimension,
153    N::Dim: DimDiv<<N::Dim as DimDiv<D::Dim>>::Output>,
154    <N::Dim as DimDiv<<N::Dim as DimDiv<D::Dim>>::Output>>::Output: Dimension,
155{
156    type Scalar = S;
157    type Out = D;
158    fn simplify(self) -> Quantity<D, S> {
159        Quantity::new(self.value())
160    }
161}