engineering_repr/
string.rs

1//! String conversions
2
3use 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"; // μ is not ASCII, which confounds things a little
13
14fn 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) => "μ", // special case as non-ASCII
20        (Ordering::Less, _) => &NEGATIVE_MULTIPLIERS[abs..=abs],
21    }
22}
23
24const fn multiplier_to_exponent(prefix: char) -> Option<i8> {
25    Some(match prefix {
26        //' ' => 0,
27        '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 /* index */, i8 /* exponent */)> {
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
60/////////////////////////////////////////////////////////////////////////
61// STRING TO NUMBER
62
63impl<T: EQSupported<T> + FromStr> FromStr for EngineeringQuantity<T> {
64    type Err = Error;
65
66    /// # Example
67    /// ```
68    /// use engineering_repr::EngineeringQuantity as EQ;
69    /// use std::str::FromStr as _;
70    /// let eq = EQ::<i64>::from_str("1.5k").unwrap();
71    /// assert_eq!(i64::try_from(eq).unwrap(), 1500);
72    /// // RKM style strings
73    /// let eq2 = EQ::<i64>::from_str("1k5").unwrap();
74    /// assert_eq!(eq, eq2);
75    /// ```
76    fn from_str(s: &str) -> Result<Self, Self::Err> {
77        let prefix = find_multiplier(s);
78        // Is there a decimal? If so it's standard (non RKM) mode.
79        let decimal = s.find('.');
80        let (prefix_index, exponent) = match (prefix, decimal) {
81            // Easy case: direct integer conversion
82            (None, None) => {
83                return T::from_str(s)
84                    .map_err(|_| Error::ParseError)
85                    .and_then(|i| EngineeringQuantity::from_raw(i, 0));
86            }
87            // 1.23 (no multiplier suffix)
88            (None, Some(idx)) => (idx, 0),
89            // General case
90            (Some((id, exp)), _) => (id, exp),
91        };
92
93        let split_index = if let Some(d) = decimal {
94            // Non-RKM mode (1.5k)
95            d
96        } else {
97            // RKM mode (1k5)
98            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        // In non-RKM mode, don't convert the prefix (err, the suffix)
105        if decimal.is_some() && prefix.is_some() {
106            let _ = trailing.pop();
107        }
108
109        // Each 3 digits (or part thereof) represents another exponent.
110        to_convert.push_str(&trailing);
111        // If it's not a round multiple of 3, we need to pad !
112        #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
113        let whole_groups = (trailing.len() / 3) as i8;
114        // convert to signed so we can trap a panic
115        #[allow(clippy::cast_possible_wrap)]
116        let mut exponent = exponent;
117        match trailing.len() % 3 {
118            0 => exponent -= whole_groups,
119            n => {
120                // n must be 1 or 2
121                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
136/////////////////////////////////////////////////////////////////////////
137// NUMBER TO STRING
138
139impl<T: EQSupported<T>> Display for EngineeringQuantity<T> {
140    /// Default behaviour is to output to 3 significant figures, skip unnecessary trailing zeros,
141    /// standard (not RKM) mode.
142    /// See [`EngineeringQuantity::default()`].
143    /// # Examples
144    /// ```
145    /// use engineering_repr::EngineeringQuantity as EQ;
146    /// let ee1 = EQ::<i32>::from(1200);
147    /// assert_eq!(ee1.to_string(), "1.2k");
148    /// let ee2 = EQ::<i32>::from(123456);
149    /// assert_eq!(ee2.to_string(), "123k");
150    /// ```
151    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/// A wrapper type which allows you to specify the desired output format.
161/// It implements [`Display`].
162///
163/// This type may be conveniently created by [`EngineeringQuantity::with_precision()`]
164/// and [`EngineeringQuantity::rkm_with_precision()`].
165#[derive(Copy, Clone, Debug)]
166pub struct DisplayAdapter<T: EQSupported<T>>
167where
168    T: ToString,
169{
170    /// The value to be displayed
171    pub value: EngineeringQuantity<T>,
172    /// The precision at which to display, or 0 to work it out losslessly
173    pub max_significant_figures: usize,
174    /// Specifies [RKM code](https://en.wikipedia.org/wiki/RKM_code) mode
175    pub rkm: bool,
176    /// Always emit the precision requested, even any unnecessary untrailing zeroes after the decimal point.
177    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    /// This is intended for use in tests.
196    #[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    /// Creates a standard [`DisplayAdapter`] for this object, with the given precision.
204    /// ```
205    /// use engineering_repr::EngineeringQuantity as EQ;
206    /// let ee = EQ::<i32>::from(1234567);
207    /// assert_eq!(ee.with_precision(2).to_string(), "1.2M");
208    /// ```
209    #[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    /// Creates an RKM [`DisplayAdapter`] for this object in RKM mode, with the given precision.
219    /// ```
220    /// use engineering_repr::EngineeringQuantity as EQ;
221    /// let ee = EQ::<i32>::from(1234567);
222    /// assert_eq!(ee.rkm_with_precision(2).to_string(), "1M2");
223    /// ```
224    #[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    /// Creates a [`DisplayAdapter`] for this object, with strict precision.
234    /// The requested digits will always be output, even trailing zeroes.
235    /// ```
236    /// use engineering_repr::EngineeringQuantity as EQ;
237    /// let ee = EQ::<i32>::from(1_200);
238    /// assert_eq!(ee.with_strict_precision(3).to_string(), "1.20k");
239    /// ```
240    #[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        /*
254         * We prepare the output string in five parts:
255         * - Prefix   := "-" (negative) or "" (positive)
256         * - Leaders  := Digits before output decimal point
257         * - Point    := Output decimal point. This is "." (normal mode), or multiplier (rkm mode), or "" (normal mode and there are no trailers)
258         * - Trailers := Digits after output decimal point
259         * - Suffix   := multiplier (normal mode) or "" (rkm mode)
260         *
261         * Algorithm:
262         * 1. Convert significand to digits
263         * 2. Compute the output exponent such that the quantity to the left of the output decimal point is from 1 to 999
264         *    (Positive exponents) Append zeroes in groups of 3 until we reach the true decimal point
265         *    (Negative exponents) Append nothing
266         * 3. Split into leading/trailing (this is a function of the exponent)
267         * 4. Implement precision:
268         *    If precision is arbitrary, trim all trailing zeroes.
269         *    Otherwise, trim trailing digits as necessary to meet the request.
270         */
271        let detail = self.value.significand.abs_and_sign();
272        let mut digits = detail.abs.to_string();
273        // at first glance the output might reasonably be this value of `digits`, followed by `exponent` times "000"...
274        // but we need to (re)compute the correct exponent for display.
275        let prefix = if detail.negative { "-" } else { "" };
276        #[allow(clippy::cast_possible_truncation)]
277        let output_exponent = if self.value.exponent > 0 {
278            // Append zeroes until we reach the decimal point (we may trim some later)
279            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            // Negative or zero exponent: Append nothing, but we need a different formula for the output exponent
286            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, // automatic mode: take the digits we've got from a full conversion, we'll trim trailing 0s in a moment
300            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            // number of digits remaining
310            digits.len() - n_leading,
311            // number of digits we'd take to reach the requested precision
312            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        // Point and suffix strings resolve to a 3-boolean truth table...
322        let (point, suffix) = match (output_exponent == 0, self.rkm, trailers.is_empty()) {
323            // Output exponent is 0: mode is irrelevant, no suffix, suppress point if there are no digits after it
324            (true, _, true) => ("", ""),
325            (true, _, false) => (".", ""),
326
327            // Exponent non zero, RKM mode: point is always SI, no suffix
328            (false, true, _) => (si, ""),
329            // Exponent non zero, Standard mode:
330            (false, false, true) => ("", si), // No trailer, suppress point
331            (false, false, false) => (".", si), // With trailer, output point
332        };
333        write!(f, "{prefix}{leaders}{point}{trailers}{suffix}")
334    }
335}
336
337/////////////////////////////////////////////////////////////////////////
338// CONVENIENCE TRAITS
339
340/// A convenience trait for outputting integers directly in engineering notation.
341///
342/// [`DisplayAdapter`] implements [`Display`], so you can use the returned adapter
343/// directly in a formatting macro.
344pub trait EngineeringRepr<T: EQSupported<T>> {
345    /// Outputs a number in engineering notation
346    ///
347    /// A request for 0 significant figures outputs exactly as many digits are necessary to maintain precision.
348    /// ```
349    /// use engineering_repr::EngineeringRepr as _;
350    /// assert_eq!("123k", 123456.to_eng(3));
351    /// assert_eq!("123.4k", 123456.to_eng(4));
352    /// assert_eq!("123.456k", 123456.to_eng(0));
353    /// ```
354    /// # Panics
355    /// If the value could not be rendered
356    fn to_eng(self, sig_figures: usize) -> DisplayAdapter<T>;
357
358    /// Outputs a number in RKM notation
359    ///
360    /// A request for 0 significant figures outputs exactly as many digits are necessary to maintain precision.
361    /// ```
362    /// use engineering_repr::EngineeringRepr as _;
363    /// assert_eq!("123k", 123456.to_rkm(3));
364    /// assert_eq!("123k4", 123456.to_rkm(4));
365    /// assert_eq!("123k456", 123456.to_rkm(0));
366    /// ```
367    /// # Panics
368    /// If the value could not be rendered
369    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/////////////////////////////////////////////////////////////////////////
390
391#[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"), // I wonder if 1R means 1 ohm or 1 ronnaohm? :-)
433            (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}