1use core::cmp::Ordering;
6use irox_tools::cfg_feature_alloc;
7
8#[allow(unused_imports)]
9use irox_tools::f64::FloatExt;
10
11cfg_feature_alloc! {
12 extern crate alloc;
13 use alloc::format;
14}
15
16#[derive(Debug, Copy, Clone)]
17pub struct SIPrefix {
18 name: &'static str,
19 symbol: &'static str,
20 base_exponent: i8,
21 scale_factor: f64,
22}
23impl PartialEq for SIPrefix {
24 fn eq(&self, other: &Self) -> bool {
25 self.base_exponent == other.base_exponent
26 }
27}
28impl Eq for SIPrefix {}
29impl PartialOrd for SIPrefix {
30 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
31 Some(self.cmp(other))
32 }
33}
34impl Ord for SIPrefix {
35 fn cmp(&self, other: &Self) -> Ordering {
36 self.base_exponent.cmp(&other.base_exponent)
37 }
38}
39impl SIPrefix {
40 #[must_use]
41 pub const fn new(
42 name: &'static str,
43 symbol: &'static str,
44 base_exponent: i8,
45 scale_factor: f64,
46 ) -> SIPrefix {
47 Self {
48 name,
49 symbol,
50 base_exponent,
51 scale_factor,
52 }
53 }
54 #[must_use]
55 pub const fn base_exponent(&self) -> i8 {
56 self.base_exponent
57 }
58 #[must_use]
59 pub const fn name(&self) -> &'static str {
60 self.name
61 }
62 #[must_use]
63 pub const fn symbol(&self) -> &'static str {
64 self.symbol
65 }
66 #[must_use]
67 pub const fn scale_factor(&self) -> f64 {
68 self.scale_factor
69 }
70
71 cfg_feature_alloc! {
72 pub fn format<T: irox_tools::ToF64>(&self, t: &T) -> alloc::string::String {
73 let val = t.to_f64() / self.scale_factor;
74 format!("{val:.3}{}", self.symbol)
75 }
76 pub fn format_args<T: irox_tools::ToF64>(&self, fmt: PrefixFormat, t: &T) -> alloc::string::String {
77 let val = t.to_f64() / self.scale_factor;
78
79 format!("{val:precision$.width$}{}", self.symbol, width = fmt.width, precision = fmt.precision)
80 }
81 }
82
83 pub fn display<T: irox_tools::ToF64>(
84 &self,
85 t: &T,
86 f: &mut core::fmt::Formatter<'_>,
87 ) -> core::fmt::Result {
88 let val = t.to_f64() / self.scale_factor;
89 core::write!(f, "{val:.3}{}", self.symbol)
90 }
91}
92
93#[derive(Debug, Clone, Copy, Default)]
94pub struct PrefixFormat {
95 width: usize,
96 precision: usize,
97}
98impl PrefixFormat {
99 #[must_use]
100 pub fn new() -> Self {
101 Self {
102 precision: 0,
103 width: 0,
104 }
105 }
106 #[must_use]
107 pub fn with_width(mut self, width: usize) -> Self {
108 self.width = width;
109 self
110 }
111 #[must_use]
112 pub fn with_precision(mut self, precision: usize) -> Self {
113 self.precision = precision;
114 self
115 }
116}
117
118pub const QUETTA: SIPrefix = SIPrefix::new("quetta", "Q", 30, 1e30);
119pub const RONNA: SIPrefix = SIPrefix::new("ronna", "R", 27, 1e27);
120pub const YOTTA: SIPrefix = SIPrefix::new("yotta", "Y", 24, 1e24);
121pub const ZETTA: SIPrefix = SIPrefix::new("zeta", "Z", 21, 1e21);
122pub const EXA: SIPrefix = SIPrefix::new("exa", "E", 18, 1e18);
123pub const PETA: SIPrefix = SIPrefix::new("peta", "P", 15, 1e15);
124pub const TERA: SIPrefix = SIPrefix::new("tera", "T", 12, 1e12);
125pub const GIGA: SIPrefix = SIPrefix::new("giga", "G", 9, 1e9);
126pub const MEGA: SIPrefix = SIPrefix::new("mega", "M", 6, 1e6);
127pub const KILO: SIPrefix = SIPrefix::new("kilo", "k", 3, 1e3);
128pub const HECTO: SIPrefix = SIPrefix::new("hecto", "h", 2, 1e2);
129pub const DECA: SIPrefix = SIPrefix::new("deca", "da", 1, 1e1);
130pub const DECI: SIPrefix = SIPrefix::new("deci", "d", -1, 1e-1);
131pub const CENTI: SIPrefix = SIPrefix::new("centi", "c", -2, 1e-2);
132pub const MILLI: SIPrefix = SIPrefix::new("milli", "m", -3, 1e-3);
133pub const MICRO: SIPrefix = SIPrefix::new("micro", "\u{03BC}", -6, 1e-6);
134pub const NANO: SIPrefix = SIPrefix::new("nano", "n", -9, 1e-9);
135pub const PICO: SIPrefix = SIPrefix::new("pico", "p", -12, 1e-12);
136pub const FEMTO: SIPrefix = SIPrefix::new("femto", "f", -15, 1e-15);
137pub const ATTO: SIPrefix = SIPrefix::new("atto", "a", -18, 1e-18);
138pub const ZEPTO: SIPrefix = SIPrefix::new("zepto", "z", -21, 1e-21);
139pub const YOCTO: SIPrefix = SIPrefix::new("yocto", "y", -24, 1e-24);
140pub const RONTO: SIPrefix = SIPrefix::new("ronto", "r", -27, 1e-27);
141pub const QUECTO: SIPrefix = SIPrefix::new("quecto", "q", -30, 1e-30);
142
143pub const ALL_PREFIXES: &[SIPrefix] = &[
145 QUETTA, RONNA, YOTTA, ZETTA, EXA, PETA, TERA, GIGA, MEGA, KILO, HECTO, DECA, DECI, CENTI,
146 MILLI, MICRO, NANO, PICO, FEMTO, ATTO, ZEPTO, YOCTO, RONTO, QUECTO,
147];
148pub const COMMON_PREFIXES: &[SIPrefix] = &[
150 YOTTA, ZETTA, EXA, PETA, TERA, GIGA, MEGA, KILO, MILLI, MICRO, NANO, PICO, FEMTO, ATTO, ZEPTO,
151 YOCTO,
152];
153
154#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
155pub enum PrefixSet {
156 All,
157 #[default]
158 Common,
159}
160impl PrefixSet {
161 #[must_use]
162 pub fn prefixes(&self) -> &'static [SIPrefix] {
163 match self {
164 Self::All => ALL_PREFIXES,
165 Self::Common => COMMON_PREFIXES,
166 }
167 }
168 pub fn best_prefix_for<T: irox_tools::ToF64>(&self, t: &T) -> Option<SIPrefix> {
169 let v = t.to_f64().abs();
170 let e = v.log10();
171 if (0. ..1.).contains(&e) {
172 return None;
173 }
174 let mut last_matched = None;
175 let fixes: &'static [SIPrefix] = self.prefixes();
176 for prefix in fixes {
177 let exp = prefix.base_exponent as f64;
178
179 last_matched = Some(*prefix);
180 if exp <= e {
181 break;
182 }
183 }
184 if let Some(lm) = last_matched {
185 let var = e - lm.base_exponent as f64;
186 if !(0. ..3.).contains(&var) {
187 return None;
188 }
189 }
190 last_matched
191 }
192}
193
194#[cfg(test)]
195mod test {
196 use crate::prefixes::{
197 PrefixSet, ATTO, CENTI, DECA, DECI, EXA, FEMTO, GIGA, HECTO, KILO, MEGA, MICRO, MILLI,
198 NANO, PETA, PICO, QUECTO, QUETTA, RONNA, RONTO, TERA, YOCTO, YOTTA, ZEPTO, ZETTA,
199 };
200
201 macro_rules! impl_test {
202 ($name:ident, $com:expr, $v:literal, $all:expr) => {
203 #[test]
204 pub fn $name() {
205 assert_eq!($com, PrefixSet::Common.best_prefix_for(&$v), "{:e}", $v);
206 assert_eq!($all, PrefixSet::All.best_prefix_for(&$v), "{:e}", $v);
207
208 let v: f64 = ($v as f64).abs();
209 let f = 10f64.powf(v.log10() + 0.3f64);
210 assert_eq!($com, PrefixSet::Common.best_prefix_for(&f), "{v:e} {f:e}");
211 let f = 10f64.powf(v.log10() + 0.7f64);
212 assert_eq!($com, PrefixSet::Common.best_prefix_for(&f), "{v:e} {f:e}");
213 }
214 };
215 }
216 impl_test!(test_quecto_30, None, 1e-30, Some(QUECTO));
217 impl_test!(test_quecto_29, None, 1e-29, Some(QUECTO));
218 impl_test!(test_quecto_28, None, 1e-28, Some(QUECTO));
219
220 impl_test!(test_ronto_27, None, 1e-27, Some(RONTO));
221 impl_test!(test_ronto_26, None, 1e-26, Some(RONTO));
222 impl_test!(test_ronto_25, None, 1e-25, Some(RONTO));
223
224 impl_test!(test_yocto_24, Some(YOCTO), 1e-24, Some(YOCTO));
225 impl_test!(test_yocto_23, Some(YOCTO), 1e-23, Some(YOCTO));
226 impl_test!(test_yocto_22, Some(YOCTO), 1e-22, Some(YOCTO));
227
228 impl_test!(test_zepto_21, Some(ZEPTO), 1e-21, Some(ZEPTO));
229 impl_test!(test_zepto_20, Some(ZEPTO), 1e-20, Some(ZEPTO));
230 impl_test!(test_zepto_19, Some(ZEPTO), 1e-19, Some(ZEPTO));
231
232 impl_test!(test_atto_18, Some(ATTO), 1e-18, Some(ATTO));
233 impl_test!(test_atto_17, Some(ATTO), 1e-17, Some(ATTO));
234 impl_test!(test_atto_16, Some(ATTO), 1e-16, Some(ATTO));
235
236 impl_test!(test_femto_15, Some(FEMTO), 1e-15, Some(FEMTO));
237 impl_test!(test_femto_14, Some(FEMTO), 1e-14, Some(FEMTO));
238 impl_test!(test_femto_13, Some(FEMTO), 1e-13, Some(FEMTO));
239
240 impl_test!(test_pico_12, Some(PICO), 1e-12, Some(PICO));
241 impl_test!(test_pico_11, Some(PICO), 1e-11, Some(PICO));
242 impl_test!(test_pico_10, Some(PICO), 1e-10, Some(PICO));
243
244 impl_test!(test_nano_09, Some(NANO), 1e-9, Some(NANO));
245 impl_test!(test_nano_08, Some(NANO), 1e-8, Some(NANO));
246 impl_test!(test_nano_07, Some(NANO), 1e-7, Some(NANO));
247
248 impl_test!(test_micro_06, Some(MICRO), 1e-6, Some(MICRO));
249 impl_test!(test_micro_m06, Some(MICRO), -1e-6, Some(MICRO));
250 impl_test!(test_micro_05, Some(MICRO), 1e-5, Some(MICRO));
251 impl_test!(test_micro_m05, Some(MICRO), -1e-5, Some(MICRO));
252 impl_test!(test_micro_04, Some(MICRO), 1e-4, Some(MICRO));
253 impl_test!(test_micro_m04, Some(MICRO), -1e-4, Some(MICRO));
254
255 impl_test!(test_milli_03, Some(MILLI), 1e-3, Some(MILLI));
256 impl_test!(test_milli_02, Some(MILLI), 1e-2, Some(CENTI));
257 impl_test!(test_milli_01, Some(MILLI), 1e-1, Some(DECI));
258
259 impl_test!(test_none, None, 1e0, None);
260 impl_test!(test_deca_01, None, 1e1, Some(DECA));
261 impl_test!(test_hecto_02, None, 1e2, Some(HECTO));
262
263 impl_test!(test_kilo_03, Some(KILO), 1e3, Some(KILO));
264 impl_test!(test_kilo_04, Some(KILO), 1e4, Some(KILO));
265 impl_test!(test_kilo_05, Some(KILO), 1e5, Some(KILO));
266
267 impl_test!(test_mega_06, Some(MEGA), 1e6, Some(MEGA));
268 impl_test!(test_mega_07, Some(MEGA), 1e7, Some(MEGA));
269 impl_test!(test_mega_08, Some(MEGA), 1e8, Some(MEGA));
270
271 impl_test!(test_giga_09, Some(GIGA), 1e9, Some(GIGA));
272 impl_test!(test_giga_10, Some(GIGA), 1e10, Some(GIGA));
273 impl_test!(test_giga_11, Some(GIGA), 1e11, Some(GIGA));
274
275 impl_test!(test_tera_12, Some(TERA), 1e12, Some(TERA));
276 impl_test!(test_tera_13, Some(TERA), 1e13, Some(TERA));
277 impl_test!(test_tera_14, Some(TERA), 1e14, Some(TERA));
278
279 impl_test!(test_peta_15, Some(PETA), 1e15, Some(PETA));
280 impl_test!(test_peta_16, Some(PETA), 1e16, Some(PETA));
281 impl_test!(test_peta_17, Some(PETA), 1e17, Some(PETA));
282
283 impl_test!(test_exa_18, Some(EXA), 1e18, Some(EXA));
284 impl_test!(test_exa_19, Some(EXA), 1e19, Some(EXA));
285 impl_test!(test_exa_20, Some(EXA), 1e20, Some(EXA));
286
287 impl_test!(test_zetta_21, Some(ZETTA), 1e21, Some(ZETTA));
288 impl_test!(test_zetta_22, Some(ZETTA), 1e22, Some(ZETTA));
289 impl_test!(test_zetta_23, Some(ZETTA), 1e23, Some(ZETTA));
290
291 impl_test!(test_yotta_24, Some(YOTTA), 1e24, Some(YOTTA));
292 impl_test!(test_yotta_25, Some(YOTTA), 1e25, Some(YOTTA));
293 impl_test!(test_yotta_26, Some(YOTTA), 1e26, Some(YOTTA));
294
295 impl_test!(test_ronna_27, None, 1e27, Some(RONNA));
296 impl_test!(test_ronna_28, None, 1e28, Some(RONNA));
297 impl_test!(test_ronna_29, None, 1e29, Some(RONNA));
298 impl_test!(test_quetta_30, None, 1e30, Some(QUETTA));
299 impl_test!(test_quetta_31, None, 1e31, Some(QUETTA));
300
301 #[cfg(feature = "alloc")]
302 #[test]
303 pub fn test_format() {
304 use crate::prefixes::PrefixFormat;
305 assert_eq!("2.000k", KILO.format(&2e3));
306 assert_eq!(
307 "2.25k",
308 KILO.format_args(
309 PrefixFormat::new().with_width(2).with_precision(4),
310 &2.2501e3
311 )
312 );
313 }
314}