irox_units/
quantities.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2025 IROX Contributors
3//
4
5use core::ops::{Deref, DerefMut};
6use irox_tools::{cfg_feature_std, ToF64};
7
8#[allow(unused_imports)]
9use irox_tools::f64::FloatExt;
10
11#[derive(Debug, Copy, Clone, Eq, PartialEq)]
12pub enum Units {
13    Gram,
14    Meter,
15    SquareMeter,
16    CubicMeter,
17    MeterPerSecond,
18    MeterPerSecondPerSecond,
19    Second,
20    Mole,
21    Ampere,
22    Kelvin,
23    Candela,
24    Newton,
25    Joule,
26    Katal,
27    Coulomb,
28    Celsius,
29    Lux,
30    Lumen,
31    Farad,
32    Weber,
33    Watt,
34    Pascal,
35    Gray,
36    Becquerel,
37    Henry,
38    Volt,
39    Ohm,
40    Steradian,
41    Radian,
42    Siemens,
43    Tesla,
44    Hertz,
45    Sievert,
46    Other {
47        name: &'static str,
48        symbol: &'static str,
49    },
50}
51
52impl Units {
53    pub fn name(&self) -> &'static str {
54        match self {
55            Units::Gram => "Gram",
56            Units::Meter => "Meter",
57            Units::SquareMeter => "SquareMeter",
58            Units::CubicMeter => "CubicMeter",
59            Units::MeterPerSecond => "MeterPerSecond",
60            Units::MeterPerSecondPerSecond => "MeterPerSecondPerSecond",
61            Units::Second => "Second",
62            Units::Mole => "Mole",
63            Units::Ampere => "Ampere",
64            Units::Kelvin => "Kelvin",
65            Units::Candela => "Candela",
66            Units::Newton => "Newton",
67            Units::Joule => "Joule",
68            Units::Katal => "Katal",
69            Units::Coulomb => "Coulomb",
70            Units::Celsius => "Celsius",
71            Units::Lux => "Lux",
72            Units::Lumen => "Lumen",
73            Units::Farad => "Farad",
74            Units::Weber => "Weber",
75            Units::Watt => "Watt",
76            Units::Pascal => "Pascal",
77            Units::Gray => "Gray",
78            Units::Becquerel => "Becquerel",
79            Units::Henry => "Henry",
80            Units::Volt => "Volt",
81            Units::Ohm => "Ohm",
82            Units::Steradian => "Steradian",
83            Units::Radian => "Radian",
84            Units::Siemens => "Siemens",
85            Units::Tesla => "Tesla",
86            Units::Hertz => "Hertz",
87            Units::Sievert => "Sievert",
88            Units::Other { name, symbol: _ } => name,
89        }
90    }
91    pub fn symbol(&self) -> &'static str {
92        match self {
93            Units::Gram => "g",
94            Units::Meter => "m",
95            Units::SquareMeter => "m\u{00B2}",
96            Units::CubicMeter => "m\u{00B3}",
97            Units::MeterPerSecond => "m/s",
98            Units::MeterPerSecondPerSecond => "m/s\u{00B2}",
99            Units::Second => "s",
100            Units::Mole => "mol",
101            Units::Ampere => "A",
102            Units::Kelvin => "K",
103            Units::Candela => "cd",
104            Units::Newton => "N",
105            Units::Joule => "J",
106            Units::Katal => "kat",
107            Units::Coulomb => "C",
108            Units::Celsius => "\u{00B0}C",
109            Units::Lux => "lx",
110            Units::Lumen => "lm",
111            Units::Farad => "F",
112            Units::Weber => "Wb",
113            Units::Watt => "W",
114            Units::Pascal => "Pa",
115            Units::Gray => "Gy",
116            Units::Becquerel => "Bq",
117            Units::Henry => "H",
118            Units::Volt => "V",
119            Units::Ohm => "\u{03A9}",
120            Units::Steradian => "sr",
121            Units::Radian => "rad",
122            Units::Siemens => "S",
123            Units::Tesla => "T",
124            Units::Hertz => "Hz",
125            Units::Sievert => "Sv",
126            Units::Other { name: _, symbol } => symbol,
127        }
128    }
129
130    cfg_feature_std! {
131        pub fn format<T: ToF64>(&self, v: &T) -> String {
132            let value = v.to_f64();
133            if let Some(prefix) = crate::prefixes::PrefixSet::Common.best_prefix_for(&value) {
134                let scale = value / prefix.scale_factor();
135                format!("{scale:.3}{}{}", prefix.symbol(), self.symbol())
136            } else {
137                format!("{:.3}{}", value, self.symbol() )
138            }
139        }
140    }
141}
142
143#[derive(Debug, Copy, Clone)]
144pub struct Quantity<T: ToF64> {
145    value: T,
146    unit: Units,
147}
148impl<T: ToF64> Quantity<T> {
149    #[must_use]
150    pub const fn new(value: T, unit: Units) -> Self {
151        Self { value, unit }
152    }
153    #[must_use]
154    pub const fn unit(&self) -> &Units {
155        &self.unit
156    }
157    #[must_use]
158    pub fn value(&self) -> &T {
159        &self.value
160    }
161}
162impl<T: ToF64> Deref for Quantity<T> {
163    type Target = T;
164    fn deref(&self) -> &Self::Target {
165        &self.value
166    }
167}
168impl<T: ToF64> DerefMut for Quantity<T> {
169    fn deref_mut(&mut self) -> &mut Self::Target {
170        &mut self.value
171    }
172}
173
174cfg_feature_std! {
175    use core::fmt::{Display, Formatter};
176    impl<T: ToF64> Display for Quantity<T> {
177        fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
178            f.write_str(&self.unit.format(self.value()))
179        }
180    }
181}
182
183pub const ONE_HYPERFINE_SECOND: Quantity<u64> = Quantity::new(9_192_631_770, Units::Hertz);
184pub const SPEED_OF_LIGHT_VACUUM: Quantity<u64> = Quantity::new(299_792_458, Units::MeterPerSecond);
185pub const ELEMENTARY_CHARGE: Quantity<f64> = Quantity::new(1.602176634e-19, Units::Coulomb);
186
187#[cfg(all(test, feature = "alloc"))]
188mod test {
189    use crate::quantities::{Quantity, Units};
190
191    #[test]
192    pub fn test() {
193        assert_eq!("1.025mV", Units::Volt.format(&1.025e-3));
194        assert_eq!("10.250nV", Units::Volt.format(&1.025e-8));
195
196        let mut q = Quantity::new(1.0256e-3, Units::Volt);
197        assert_eq!("1.026mV", q.to_string());
198        assert_eq!("1.026mV", format!("{q}"));
199        *q = 1.025e-8;
200        assert_eq!("10.250nV", q.to_string());
201        assert_eq!("10.250nV", format!("{q}"));
202        *q = 1.025e4;
203        assert_eq!("10.250kV", q.to_string());
204        assert_eq!("10.250kV", format!("{q}"));
205
206        let q = Quantity::new(1.0256e-8, Units::Ohm);
207        assert_eq!("10.256n\u{03A9}", q.to_string());
208
209        let q = Quantity::new(1.0256e-8, Units::Celsius);
210        assert_eq!("10.256n\u{00B0}C", q.to_string());
211
212        let q = Quantity::new(1.0256e-8, Units::SquareMeter);
213        assert_eq!("10.256nm\u{00B2}", q.to_string());
214        let q = Quantity::new(1.0256e-8, Units::CubicMeter);
215        assert_eq!("10.256nm\u{00B3}", q.to_string());
216        let q = Quantity::new(1.0256e-8, Units::MeterPerSecondPerSecond);
217        assert_eq!("10.256nm/s\u{00B2}", q.to_string());
218    }
219}