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 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 (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 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 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}