shared/
unit.rs

1use core::fmt::{self, Debug, Display};
2
3pub struct Suffix {
4    pub name: &'static str,
5    pub symbol: &'static str,
6}
7
8pub type PrefixTuple = (i32, &'static str, &'static str);
9
10pub const BYTES_SUFFIX: Suffix = Suffix {
11    name: "bytes",
12    symbol: "B",
13};
14pub const BITS_SUFFIX: Suffix = Suffix {
15    name: "bits",
16    symbol: "b",
17};
18pub const FREQUENCY_SUFFIX: Suffix = Suffix {
19    name: "hertz",
20    symbol: "Hz",
21};
22
23pub const PREFIXES: &[PrefixTuple] = &[
24    (30, "quetta", "Q"),
25    (27, "ronna", "R"),
26    (24, "yotta", "Y"),
27    (21, "zetta", "Z"),
28    (18, "exa", "E"),
29    (15, "peta", "P"),
30    (12, "tera", "T"),
31    (9, "giga", "G"),
32    (6, "mega", "M"),
33    (3, "kilo", "k"),
34    (2, "hecto", "h"),
35    (1, "deca", "da"),
36    (0, "", ""),
37    (-1, "deci", "d"),
38    (-2, "centi", "c"),
39    (-3, "milli", "m"),
40    (-6, "micro", "µ"),
41    (-9, "nano", "n"),
42    (-12, "pico", "p"),
43    (-15, "femto", "f"),
44    (-18, "atto", "a"),
45    (-21, "zepto", "z"),
46    (-24, "yocto", "y"),
47    (-27, "ronto", "r"),
48    (-30, "quecto", "q"),
49];
50
51pub struct Unit<'a, T> {
52    pub value: T,
53    pub prefix: PrefixTuple,
54    pub suffix: &'a str,
55}
56
57fn get_prefix<T>(value: T) -> PrefixTuple
58where
59    T: Copy + PartialOrd + TryInto<f64>,
60{
61    let target_value: f64 = value.try_into().ok().unwrap_or(0.0).abs();
62
63    // Find the largest prefix where the value is >= prefix_value
64    for &prefix @ (exponent, _, _) in PREFIXES {
65        let prefix_value: f64 = 10f64.powi(exponent);
66
67        if target_value >= prefix_value {
68            return prefix;
69        }
70    }
71
72    // Default to base unit if no prefix matches
73    (0, "", "")
74}
75
76impl<'a, T> Unit<'a, T>
77where
78    T: Copy + PartialOrd + TryInto<f64>,
79{
80    pub fn new(value: T, suffix: &'a str) -> Self {
81        let prefix = get_prefix(value);
82        Self {
83            value,
84            prefix,
85            suffix,
86        }
87    }
88
89    pub fn with_custom_prefix(value: T, prefix: PrefixTuple, suffix: &'a str) -> Self {
90        Self {
91            value,
92            prefix,
93            suffix,
94        }
95    }
96
97    pub fn get_prefix(&self) -> PrefixTuple {
98        self.prefix
99    }
100
101    pub fn format(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
102        let (exponent, _, symbol) = self.prefix;
103
104        let scaled_value: f64 = self.value.try_into().ok().unwrap_or(0.0) / 10f64.powi(exponent);
105
106        // Check if the value has a fractional part
107        if scaled_value.fract() == 0.0 {
108            write!(fmt, "{} {}{}", scaled_value, symbol, self.suffix)
109        } else {
110            write!(fmt, "{:.2} {}{}", scaled_value, symbol, self.suffix)
111        }
112    }
113
114    pub fn format_full_name(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
115        let (exponent, name, _) = self.prefix;
116
117        let scaled_value: f64 = self.value.try_into().ok().unwrap_or(0.0) / 10f64.powi(exponent);
118
119        // Check if the value has a fractional part
120        if scaled_value.fract() == 0.0 {
121            write!(fmt, "{} {}{}", scaled_value, name, self.suffix)
122        } else {
123            write!(fmt, "{:.2} {}{}", scaled_value, name, self.suffix)
124        }
125    }
126}
127
128impl<T> Debug for Unit<'_, T>
129where
130    T: Debug,
131{
132    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
133        formatter
134            .debug_struct("Unit")
135            .field("value", &self.value)
136            .field("suffix", &self.suffix)
137            .finish()
138    }
139}
140
141impl<T> Display for Unit<'_, T>
142where
143    T: Display + Copy + PartialOrd + TryInto<f64>,
144{
145    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
146        self.format(formatter)
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    extern crate alloc;
153
154    use alloc::format;
155
156    use super::*;
157
158    #[test]
159    fn test_unit_new() {
160        let unit = Unit::new(1000.0, "m");
161        assert_eq!(unit.value, 1000.0);
162        assert_eq!(unit.suffix, "m");
163    }
164
165    #[test]
166    fn test_get_prefix_base() {
167        let prefix = get_prefix(1.0);
168        assert_eq!(prefix, (0, "", ""));
169    }
170
171    #[test]
172    fn test_get_prefix_kilo() {
173        let prefix = get_prefix(1000.0);
174        assert_eq!(prefix, (3, "kilo", "k"));
175    }
176
177    #[test]
178    fn test_get_prefix_mega() {
179        let prefix = get_prefix(1_000_000.0);
180        assert_eq!(prefix, (6, "mega", "M"));
181    }
182
183    #[test]
184    fn test_get_prefix_giga() {
185        let prefix = get_prefix(1_000_000_000.0);
186        assert_eq!(prefix, (9, "giga", "G"));
187    }
188
189    #[test]
190    fn test_get_prefix_milli() {
191        let prefix = get_prefix(0.001);
192        assert_eq!(prefix, (-3, "milli", "m"));
193    }
194
195    #[test]
196    fn test_get_prefix_micro() {
197        let prefix = get_prefix(0.000001);
198        assert_eq!(prefix, (-6, "micro", "µ"));
199    }
200
201    #[test]
202    fn test_get_prefix_nano() {
203        let prefix = get_prefix(0.000000001);
204        assert_eq!(prefix, (-9, "nano", "n"));
205    }
206
207    #[test]
208    fn test_unit_format() {
209        let unit = Unit::new(1500.0, "m");
210        assert_eq!(format!("{}", unit), "1.50 km");
211    }
212
213    #[test]
214    fn test_unit_format_base() {
215        let unit = Unit::new(5.0, "g");
216        assert_eq!(format!("{}", unit), "5 g");
217    }
218
219    #[test]
220    fn test_unit_format_milli() {
221        let unit = Unit::new(0.005, "A");
222        assert_eq!(format!("{}", unit), "5 mA");
223    }
224
225    #[test]
226    fn test_unit_format_mega() {
227        let unit = Unit::new(2_500_000.0, "B");
228        assert_eq!(format!("{}", unit), "2.50 MB");
229    }
230
231    #[test]
232    fn test_unit_debug() {
233        let unit = Unit::new(42.0, "Hz");
234        let debug_str = format!("{:?}", unit);
235        assert!(debug_str.contains("value"));
236        assert!(debug_str.contains("suffix"));
237    }
238
239    #[test]
240    fn test_get_prefix_tera() {
241        let prefix = get_prefix(1_000_000_000_000.0);
242        assert_eq!(prefix, (12, "tera", "T"));
243    }
244
245    #[test]
246    fn test_get_prefix_pico() {
247        let prefix = get_prefix(0.000000000001);
248        assert_eq!(prefix, (-12, "pico", "p"));
249    }
250
251    #[test]
252    fn test_unit_format_small_value() {
253        let unit = Unit::new(0.0000025, "F");
254        assert_eq!(format!("{}", unit), "2.50 µF");
255    }
256
257    #[test]
258    fn test_unit_format_large_value() {
259        let unit = Unit::new(5_000_000_000.0, "Hz");
260        assert_eq!(format!("{}", unit), "5 GHz");
261    }
262}