Skip to main content

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