1use std::{
4 cmp::{min, Ordering},
5 fmt::Display,
6 str::FromStr,
7};
8
9use crate::{EQSupported, EngineeringQuantity, Error};
10
11static POSITIVE_MULTIPLIERS: &str = " kMGTPEZYRQ";
12static NEGATIVE_MULTIPLIERS: &str = " munpfazyrq"; fn exponent_to_multiplier(exp: i8) -> &'static str {
15 let abs = exp.unsigned_abs() as usize;
16 match (exp.cmp(&0), abs) {
17 (Ordering::Equal, _) => "",
18 (Ordering::Greater, _) => &POSITIVE_MULTIPLIERS[abs..=abs],
19 (Ordering::Less, 2) => "μ", (Ordering::Less, _) => &NEGATIVE_MULTIPLIERS[abs..=abs],
21 }
22}
23
24const fn multiplier_to_exponent(prefix: char) -> Option<i8> {
25 Some(match prefix {
26 'k' => 1,
28 'M' => 2,
29 'G' => 3,
30 'T' => 4,
31 'P' => 5,
32 'E' => 6,
33 'Z' => 7,
34 'Y' => 8,
35 'R' => 9,
36 'Q' => 10,
37 'm' => -1,
38 'μ' | 'u' => -2,
39 'n' => -3,
40 'p' => -4,
41 'f' => -5,
42 'a' => -6,
43 'z' => -7,
44 'y' => -8,
45 'r' => -9,
46 'q' => -10,
47 _ => return None,
48 })
49}
50
51fn find_multiplier(s: &str) -> Option<(usize , i8 )> {
52 for (i, c) in s.chars().enumerate() {
53 if let Some(p) = multiplier_to_exponent(c) {
54 return Some((i, p));
55 }
56 }
57 None
58}
59
60impl<T: EQSupported<T> + FromStr> FromStr for EngineeringQuantity<T> {
64 type Err = Error;
65
66 fn from_str(s: &str) -> Result<Self, Self::Err> {
77 let prefix = find_multiplier(s);
78 let decimal = s.find('.');
80 let (prefix_index, exponent) = match (prefix, decimal) {
81 (None, None) => {
83 return T::from_str(s)
84 .map_err(|_| Error::ParseError)
85 .and_then(|i| EngineeringQuantity::from_raw(i, 0));
86 }
87 (None, Some(idx)) => (idx, 0),
89 (Some((id, exp)), _) => (id, exp),
91 };
92
93 let split_index = if let Some(d) = decimal {
94 d
96 } else {
97 prefix_index
99 };
100
101 let mut to_convert = s.chars().take(split_index).collect::<String>();
102 let mut trailing = s.chars().skip(split_index + 1).collect::<String>();
103
104 if decimal.is_some() && prefix.is_some() {
106 let _ = trailing.pop();
107 }
108
109 to_convert.push_str(&trailing);
111 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
113 let whole_groups = (trailing.len() / 3) as i8;
114 #[allow(clippy::cast_possible_wrap)]
116 let mut exponent = exponent;
117 match trailing.len() % 3 {
118 0 => exponent -= whole_groups,
119 n => {
120 exponent -= whole_groups + 1;
122 to_convert.push_str("0".repeat(3 - n).as_str());
123 }
124 }
125
126 let significand = T::from_str(&to_convert).map_err(|_| Error::ParseError)?;
127 #[allow(
128 clippy::cast_possible_truncation,
129 clippy::cast_possible_wrap,
130 clippy::cast_sign_loss
131 )]
132 Self::from_raw(significand, exponent)
133 }
134}
135
136impl<T: EQSupported<T>> Display for EngineeringQuantity<T> {
140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 DisplayAdapter {
153 value: *self,
154 ..Default::default()
155 }
156 .fmt(f)
157 }
158}
159
160#[derive(Copy, Clone, Debug)]
166pub struct DisplayAdapter<T: EQSupported<T>>
167where
168 T: ToString,
169{
170 pub value: EngineeringQuantity<T>,
172 pub max_significant_figures: usize,
174 pub rkm: bool,
176 pub strict: bool,
178}
179
180impl<T: EQSupported<T>> Default for DisplayAdapter<T> {
181 fn default() -> Self {
182 Self {
183 value: EngineeringQuantity {
184 significand: T::ZERO,
185 exponent: 0,
186 },
187 max_significant_figures: 3,
188 rkm: false,
189 strict: false,
190 }
191 }
192}
193
194impl<T: EQSupported<T>> PartialEq<DisplayAdapter<T>> for &str {
195 #[allow(clippy::cmp_owned)]
197 fn eq(&self, other: &DisplayAdapter<T>) -> bool {
198 *self == other.to_string()
199 }
200}
201
202impl<T: EQSupported<T>> EngineeringQuantity<T> {
203 #[must_use]
210 pub fn with_precision(&self, max_significant_figures: usize) -> DisplayAdapter<T> {
211 DisplayAdapter {
212 value: *self,
213 max_significant_figures,
214 rkm: false,
215 strict: false,
216 }
217 }
218 #[must_use]
225 pub fn rkm_with_precision(&self, max_significant_figures: usize) -> DisplayAdapter<T> {
226 DisplayAdapter {
227 value: *self,
228 max_significant_figures,
229 rkm: true,
230 strict: false,
231 }
232 }
233 #[must_use]
241 pub fn with_strict_precision(&self, max_significant_figures: usize) -> DisplayAdapter<T> {
242 DisplayAdapter {
243 value: *self,
244 max_significant_figures,
245 rkm: false,
246 strict: true,
247 }
248 }
249}
250
251impl<T: EQSupported<T>> Display for DisplayAdapter<T> {
252 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
253 let detail = self.value.significand.abs_and_sign();
272 let mut digits = detail.abs.to_string();
273 let prefix = if detail.negative { "-" } else { "" };
276 #[allow(clippy::cast_possible_truncation)]
277 let output_exponent = if self.value.exponent > 0 {
278 digits.reserve((3 * self.value.exponent + 1).unsigned_abs() as usize);
280 for _ in 0..self.value.exponent {
281 digits.push_str("000");
282 }
283 ((digits.len() - 1) / 3) as i8
284 } else {
285 self.value.exponent + ((digits.len() - 1) / 3) as i8
287 };
288 let si = exponent_to_multiplier(output_exponent);
289
290 let n_leading = if output_exponent > 0 {
291 digits.len() - output_exponent.unsigned_abs() as usize * 3
292 } else {
293 match digits.len() % 3 {
294 0 => 3,
295 i => i,
296 }
297 };
298 let precision = match self.max_significant_figures {
299 0 => usize::MAX, i => i,
301 };
302 if self.strict {
303 let pad = self.max_significant_figures.saturating_sub(digits.len());
304 for _ in 0..pad {
305 digits.push('0');
306 }
307 }
308 let n_trailing = min(
309 digits.len() - n_leading,
311 precision - min(precision, n_leading),
313 );
314 let leaders = &digits[0..n_leading];
315 let mut trailers = &digits[n_leading..n_leading + min(n_trailing, precision)];
316 if !self.strict {
317 while trailers.ends_with('0') {
318 trailers = &trailers[0..trailers.len() - 1];
319 }
320 }
321 let (point, suffix) = match (output_exponent == 0, self.rkm, trailers.is_empty()) {
323 (true, _, true) => ("", ""),
325 (true, _, false) => (".", ""),
326
327 (false, true, _) => (si, ""),
329 (false, false, true) => ("", si), (false, false, false) => (".", si), };
333 write!(f, "{prefix}{leaders}{point}{trailers}{suffix}")
334 }
335}
336
337pub trait EngineeringRepr<T: EQSupported<T>> {
345 fn to_eng(self, sig_figures: usize) -> DisplayAdapter<T>;
357
358 fn to_rkm(self, sig_figures: usize) -> DisplayAdapter<T>;
370}
371
372macro_rules! impl_to_eng {
373 {$($t:ty),+} => {$(
374 impl<> EngineeringRepr<$t> for $t {
375 fn to_eng(self, sig_figures: usize) -> DisplayAdapter<$t>
376 {
377 EngineeringQuantity::<$t>::try_from(self).unwrap().with_precision(sig_figures)
378 }
379 fn to_rkm(self, sig_figures: usize) -> DisplayAdapter<$t>
380 {
381 EngineeringQuantity::<$t>::try_from(self).unwrap().rkm_with_precision(sig_figures)
382 }
383 }
384 )+}
385}
386
387impl_to_eng!(u16, u32, u64, u128, usize, i16, i32, i64, i128, isize);
388
389#[cfg(test)]
392mod test {
393 use super::EngineeringQuantity as EQ;
394 use std::str::FromStr as _;
395
396 #[test]
397 fn from_string() {
398 for (i, s) in &[
399 (1i128, "1"),
400 (42, "42"),
401 (999, "999"),
402 (1000, "1k"),
403 (1500, "1.5k"),
404 (2345, "2.345k"),
405 (9999, "9.999k"),
406 (12_345, "12.345k"),
407 (13_000, "13k"),
408 (13_000, "13.k"),
409 (13_000, "13.0k"),
410 (999_999, "999.999k"),
411 (1_000_000, "1.00M"),
412 (2_345_678, "2.345678M"),
413 (999_999_999, "999.999999M"),
414 (12_345_000_000_000_000_000_000_000_000, "12.345R"),
415 (12_345_000_000_000_000_000_000_000_000_000, "12.345Q"),
416 (1000, "1k0"),
417 (1500, "1k5"),
418 (2345, "2k345"),
419 (9999, "9k999"),
420 (12_345, "12k345"),
421 (13_000, "13k0"),
422 (999_999, "999k999"),
423 (1_000_000, "1M0"),
424 (2_345_678, "2M345678"),
425 (999_999_999, "999M999999"),
426 (1_000_000_000, "1G0"),
427 (1_000_000_000_000, "1T0"),
428 (1_000_000_000_000_000, "1P0"),
429 (1_000_000_000_000_000_000, "1E0"),
430 (1_000_000_000_000_000_000_000, "1Z0"),
431 (1_000_000_000_000_000_000_000_000, "1Y0"),
432 (12_345_000_000_000_000_000_000_000_000, "12R345"), (12_345_000_000_000_000_000_000_000_000_000, "12Q345"),
434 ] {
435 let eq = EQ::<i128>::from_str(s).unwrap();
436 let result = i128::from(eq);
437 assert_eq!(result, *i, "input {s} expected {i}");
438 let mut str2 = String::with_capacity(1 + s.len());
439 str2.push('-');
440 str2.push_str(s);
441 let ee2 = EQ::<i128>::from_str(&str2).unwrap();
442 assert_eq!(i128::from(ee2), -*i);
443 }
444 }
445
446 #[test]
447 fn parse_failures() {
448 for s in &["foo", "1.2.3k", "--1"] {
449 let _ = EQ::<i128>::from_str(s).expect_err(&format!("this should have failed: {s}"));
450 }
451 }
452
453 #[test]
454 fn to_string() {
455 for (i, s) in &[
456 (1i128, "1"),
457 (42, "42"),
458 (999, "999"),
459 (1000, "1k"),
460 (1500, "1.5k"),
461 (2345, "2.34k"),
462 (9999, "9.99k"),
463 (12_345, "12.3k"),
464 (13_000, "13k"),
465 (999_999, "999k"),
466 (1_000_000, "1M"),
467 (2_345_678, "2.34M"),
468 (999_999_999, "999M"),
469 (12_345_000_000_000_000_000_000_000_000, "12.3R"),
470 (12_345_000_000_000_000_000_000_000_000_000, "12.3Q"),
471 ] {
472 let ee = EQ::<i128>::from(*i);
473 assert_eq!(ee.to_string(), *s);
474 let ee2 = EQ::<i128>::from(-*i);
475 let ss2 = ee2.to_string();
476 assert_eq!(ss2.chars().next().unwrap(), '-');
477 assert_eq!(&ss2[1..], *s);
478 }
479 }
480 #[test]
481 fn to_string_small() {
482 for (i, e, s) in &[
483 (1, -1, "1m"),
484 (999, -1, "999m"),
485 (1, -2, "1μ"),
486 (1001, -2, "1m"),
487 (1001, -1, "1"),
488 (1_000_001, -2, "1"),
489 (1_111, -1, "1.11"),
490 (1010, -3, "1.01μ"),
491 (1010, -4, "1.01n"),
492 (1010, -5, "1.01p"),
493 (1010, -6, "1.01f"),
494 (1010, -7, "1.01a"),
495 (1010, -8, "1.01z"),
496 (1010, -9, "1.01y"),
497 (1010, -10, "1.01r"),
498 (1010, -11, "1.01q"),
499 ] {
500 let ee = EQ::<i128>::from_raw(*i, *e).unwrap();
501 assert_eq!(ee.to_string(), *s, "inputs {i}, {e}");
502 let ee2 = EQ::<i128>::from_raw(-*i, *e).unwrap();
503 let mut expected = (*s).to_string();
504 expected.insert(0, '-');
505 assert_eq!(ee2.to_string(), expected, "inputs -{i}, {e}");
506 }
507 for (i, e, s) in &[
508 (1, -1, "1m"),
509 (999, -1, "999m"),
510 (1, -2, "1μ"),
511 (1001, -2, "1m001"),
512 (1001, -1, "1.001"),
513 (1_000_001, -2, "1"),
514 ] {
515 let ee = EQ::<i128>::from_raw(*i, *e).unwrap();
516 assert_eq!(ee.rkm_with_precision(4).to_string(), *s, "inputs {i}, {e}");
517 }
518 }
519 #[test]
520 fn from_string_small() {
521 for (i, e, s) in &[
522 (1, -1, "1m"),
523 (999, -1, "999m"),
524 (1, -2, "1μ"),
525 (1001, -2, "1.001m"),
526 (1001, -1, "1.001"),
527 (1, 0, "1"),
528 (1_000_001, -2, "1.000001"),
529 (1_111, -1, "1.111"),
530 (1010, -3, "1.01μ"),
531 (1010, -4, "1.01n"),
532 (1010, -5, "1.01p"),
533 (1010, -6, "1.01f"),
534 (1010, -7, "1.01a"),
535 (1010, -8, "1.01z"),
536 (1010, -9, "1.01y"),
537 (1010, -10, "1.01r"),
538 (1010, -11, "1.01q"),
539 ] {
540 let ee3 = EQ::<i128>::from_str(s).unwrap();
541 let expected_raw = (*i, *e);
542 assert_eq!(ee3.to_raw(), expected_raw);
543 }
544 for (i, e, s) in &[
545 (1, -1, "1m"),
546 (999, -1, "999m"),
547 (1, -2, "1μ"),
548 (1001, -2, "1m001"),
549 (1001, -1, "1.001"),
550 (1_000_001, -2, "1.000001"),
551 ] {
552 let ee2 = EQ::<i64>::from_str(s).unwrap();
553 let expected_raw = (*i, *e);
554 assert_eq!(ee2.to_raw(), expected_raw);
555 }
556 }
557 #[test]
558 fn to_string_rkm() {
559 for (i, s) in &[
560 (1i128, "1"),
561 (42, "42"),
562 (999, "999"),
563 (1000, "1k"),
564 (1500, "1k5"),
565 (2345, "2k3"),
566 (9999, "9k9"),
567 (12_345, "12k"),
568 (13_000, "13k"),
569 (999_999, "999k"),
570 (1_000_000, "1M"),
571 (2_345_678, "2M3"),
572 (999_999_999, "999M"),
573 (12_345_000_000_000_000_000_000_000_000, "12R"),
574 (12_345_000_000_000_000_000_000_000_000_000, "12Q"),
575 ] {
576 let ee = EQ::<i128>::from(*i);
577 assert_eq!(ee.rkm_with_precision(2).to_string(), *s);
578 let ee2 = EQ::<i128>::from(-*i);
579 let ss2 = ee2.rkm_with_precision(2).to_string();
580 assert_eq!(ss2.chars().next().unwrap(), '-');
581 assert_eq!(&ss2[1..], *s);
582 }
583 }
584
585 #[test]
586 fn traits() {
587 use super::EngineeringRepr as _;
588 assert_eq!("123k", 123_456.to_eng(3));
589 assert_eq!("123.4k", 123_456.to_eng(4));
590 assert_eq!("123k4", 123_456.to_rkm(4));
591 }
592
593 #[test]
594 fn raw_to_string() {
595 for (sig, exp, str) in &[
596 (1, 0i8, "1"),
597 (1, 1, "1k"),
598 (1000, 0, "1k"),
599 (1000, 1, "1M"),
600 ] {
601 let e = EQ::<i128>::from_raw(*sig, *exp).unwrap();
602 assert_eq!(e.to_string(), *str, "test case: {sig},{exp} -> {str}");
603 }
604 }
605
606 #[test]
607 fn overflow() {
608 let e = EQ::from_raw(1u16, 0).unwrap();
609 let e2 = EQ::from_raw(1u16, 1).unwrap();
610 assert_ne!(e, e2);
611 println!("{e:?} -> {e}");
612 println!("{e2:?} -> {e2}");
613 let _ = e.to_string();
614 }
615
616 #[test]
617 fn auto_precision() {
618 for (i, s) in &[
619 (1i128, "1"),
620 (42, "42"),
621 (100, "100"),
622 (999, "999"),
623 (1000, "1k"),
624 (1500, "1.5k"),
625 (2345, "2.345k"),
626 (9999, "9.999k"),
627 (12_345, "12.345k"),
628 (13_000, "13k"),
629 (999_999, "999.999k"),
630 (1_000_000, "1M"),
631 (2_345_678, "2.345678M"),
632 (999_999_999, "999.999999M"),
633 (12_345_600_000_000_000_000_000_000_000, "12.3456R"),
634 (12_345_600_000_000_000_000_000_000_000_000, "12.3456Q"),
635 ] {
636 let ee = EQ::<i128>::from(*i);
637 assert_eq!(ee.with_precision(0).to_string(), *s, "input={}", *i);
638 let ee2 = EQ::<i128>::from(-*i);
639 let ss2 = ee2.with_precision(0).to_string();
640 assert_eq!(ss2.chars().next().unwrap(), '-');
641 assert_eq!(&ss2[1..], *s, "input={}", -*i);
642 }
643 }
644 #[test]
645 fn strict_precision() {
646 let ee = EQ::<i64>::from_raw(1234, -3).unwrap();
647 assert_eq!(ee.with_strict_precision(6).to_string(), "1.23400μ");
648 }
649}