irox_tools/
fmt.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2023 IROX Contributors
3//
4
5//!
6//! Formatting structs and traits
7//!
8
9use core::fmt::{Display, Formatter};
10extern crate alloc;
11#[allow(unused_imports)]
12use crate::f64::FloatExt;
13use alloc::string::String;
14
15///
16/// Variant of the `format!` macro that doesn't require `std::io::Write`
17#[macro_export]
18macro_rules! format {
19    ($($arg:tt)*) => {{
20        extern crate alloc;
21        use alloc::string::String;
22        use core::fmt::Write;
23
24        let mut val = String::new();
25        val.write_fmt(format_args!($($arg)*)).expect("a formatting trait implementation returned an error");
26        val
27    }};
28}
29
30///
31/// This struct allows you to print a specific number of digits before the decimal point,
32/// and after the decimal point.
33///
34/// This exists because the base format trait allows you to specify a width and a precision.
35/// However, in a fractional number, the width applies to the WHOLE number, including the fractional
36/// component, and doesn't zero-pad effectively.
37///
38/// * The first parameter is the number of zero-padded digits before the decimal point.
39/// * The second parameter is the number of zero-padded digits after the decimal point.
40///
41/// # Example:
42/// ```
43/// use irox_tools::fmt::DecimalFormatF64;
44/// assert_eq!("00.1235", format!("{}", DecimalFormatF64(2,4,0.1234567)));
45/// ```
46pub struct DecimalFormatF64(pub usize, pub usize, pub f64);
47
48impl Display for DecimalFormatF64 {
49    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
50        let mut base = self.2.trunc();
51        let width = self.0;
52        let prec = self.1;
53        let powi = 10_u64.pow(self.1 as u32) as f64;
54        let mut val = (self.2.fract().abs() * powi).round();
55        if val >= powi {
56            base += 1.;
57            val -= powi;
58        }
59        let val = val as u64;
60        write!(f, "{base:0width$}.{val:0prec$}")
61    }
62}
63
64///
65/// This struct allows you to print a specific number of digits before the decimal point,
66/// and after the decimal point.
67///
68/// This exists because the base format trait allows you to specify a width and a precision.
69/// However, in a fractional number, the width applies to the WHOLE number, including the fractional
70/// component, and doesn't zero-pad effectively.
71///
72/// * The first parameter is the number of zero-padded digits before the decimal point.
73/// * The second parameter is the number of zero-padded digits after the decimal point.
74///
75/// # Example:
76/// ```
77/// use irox_tools::fmt::DecimalFormatF32;
78/// assert_eq!("00.1235", format!("{}", DecimalFormatF32(2,4,0.1234567)));
79/// ```
80pub struct DecimalFormatF32(pub usize, pub usize, pub f32);
81
82impl Display for DecimalFormatF32 {
83    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
84        let mut base = self.2.trunc();
85        let width = self.0;
86        let prec = self.1;
87        let powi = 10_u64.pow(self.1 as u32) as f32;
88        let mut val = (self.2.fract().abs() * powi).round();
89        if val >= powi {
90            base += 1.;
91            val -= powi;
92        }
93        let val = val as u64;
94        write!(f, "{base:0width$}.{val:0prec$}")
95    }
96}
97
98///
99/// This struct allows you to print a specific number of digits before the decimal point,
100/// and after the decimal point.
101///
102/// This exists because the base format trait allows you to specify a width and a precision.
103/// However, in a fractional number, the width applies to the WHOLE number, including the fractional
104/// component, and doesn't zero-pad effectively.
105///
106/// * The first parameter is the number of zero-padded digits before the decimal point.
107/// * The second parameter is the number of zero-padded digits after the decimal point.
108///
109/// # Example:
110/// ```
111/// use irox_tools::fmt::DecimalFormat;
112/// let fmt = DecimalFormat::new(2,4);
113///
114/// assert_eq!("00.1235", fmt.format_f64(0.1234567));
115/// ```
116pub struct DecimalFormat {
117    width: usize,
118    precision: usize,
119}
120
121impl DecimalFormat {
122    pub fn new(width: usize, precision: usize) -> Self {
123        Self { width, precision }
124    }
125
126    ///
127    /// Formats the specified [`f64`] using this formatter.
128    ///
129    /// # Example:
130    /// ```
131    /// use irox_tools::fmt::DecimalFormat;
132    /// let fmt = DecimalFormat::new(2,4);
133    ///
134    /// assert_eq!("00.1235", fmt.format_f64(0.1234567));
135    /// ```
136    pub fn format_f64(&self, val: f64) -> String {
137        format!("{}", DecimalFormatF64(self.width, self.precision, val))
138    }
139
140    ///
141    /// Formats the specified [`f32`] using this formatter.
142    ///
143    /// # Example:
144    /// ```
145    /// use irox_tools::fmt::DecimalFormat;
146    /// let fmt = DecimalFormat::new(2,4);
147    ///
148    /// assert_eq!("00.1235", fmt.format_f32(0.1234567));
149    /// ```
150    pub fn format_f32(&self, val: f32) -> String {
151        format!("{}", DecimalFormatF32(self.width, self.precision, val))
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use crate::fmt::DecimalFormatF64;
158
159    #[test]
160    pub fn test() {
161        let val = 0.1234567;
162        assert_eq!("00.1235", format!("{}", DecimalFormatF64(2, 4, val)));
163
164        assert_eq!("0.123", format!("{}", DecimalFormatF64(0, 3, val)));
165        assert_eq!("0.123", format!("{}", DecimalFormatF64(1, 3, val)));
166        assert_eq!("00.123", format!("{}", DecimalFormatF64(2, 3, val)));
167        assert_eq!("00.12346", format!("{}", DecimalFormatF64(2, 5, val)));
168        assert_eq!("00.123457", format!("{}", DecimalFormatF64(2, 6, val)));
169        assert_eq!("00.1234567", format!("{}", DecimalFormatF64(2, 7, val)));
170        assert_eq!("00.12345670", format!("{}", DecimalFormatF64(2, 8, val)));
171        assert_eq!("00.123456700", format!("{}", DecimalFormatF64(2, 9, val)));
172        assert_eq!(
173            "000.1234567000",
174            format!("{}", DecimalFormatF64(3, 10, val))
175        );
176    }
177
178    #[test]
179    pub fn test2() {
180        assert_eq!("1.0", format!("{}", DecimalFormatF64(1, 0, 0.98)));
181        assert_eq!("1.0", format!("{}", DecimalFormatF64(1, 1, 0.98)));
182        assert_eq!("0.98", format!("{}", DecimalFormatF64(1, 2, 0.98)));
183        assert_eq!("0.980", format!("{}", DecimalFormatF64(1, 3, 0.98)));
184        assert_eq!("0.950", format!("{}", DecimalFormatF64(1, 3, 0.95)));
185        assert_eq!("0.940", format!("{}", DecimalFormatF64(1, 3, 0.94)));
186        assert_eq!("0.94", format!("{}", DecimalFormatF64(1, 2, 0.94)));
187        assert_eq!("0.9", format!("{}", DecimalFormatF64(1, 1, 0.94)));
188        assert_eq!("1.0", format!("{}", DecimalFormatF64(1, 0, 0.94)));
189
190        assert_eq!("0.999", format!("{}", DecimalFormatF64(1, 3, 0.999)));
191        assert_eq!("0.9990", format!("{}", DecimalFormatF64(1, 4, 0.999)));
192        assert_eq!("1.00", format!("{}", DecimalFormatF64(1, 2, 0.999)));
193
194        assert_eq!("-21.30", format!("{}", DecimalFormatF64(2, 2, -21.3)));
195        assert_eq!("-21.3", format!("{}", DecimalFormatF64(2, 1, -21.3)));
196        assert_eq!("-21.0", format!("{}", DecimalFormatF64(2, 0, -21.3)));
197    }
198}