pretty_bytes_typed/
decimal.rs

1use crate::util::round_float;
2
3/// Struct that represents prettified byte values (base-10)
4#[derive(PartialEq, Debug, Clone)]
5#[must_use]
6#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7pub struct PrettyBytes {
8    num: f64,
9    suffix: ByteValues,
10}
11
12impl std::fmt::Display for PrettyBytes {
13    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14        write!(f, "{} {:?}", self.num, self.suffix)
15    }
16}
17
18#[derive(Clone, Copy, Debug, PartialEq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20enum ByteValues {
21    B,
22    KB,
23    MB,
24    GB,
25    TB,
26    PB,
27    EB,
28}
29
30impl ByteValues {
31    // EB is the max that can be represented with a u64
32    const UNITS: [Self; 7] = [
33        Self::B,
34        Self::KB,
35        Self::MB,
36        Self::GB,
37        Self::TB,
38        Self::PB,
39        Self::EB,
40    ];
41}
42
43/// Convert a byte value to a "prettified" version
44///
45/// Converts using base-10 byte suffixes (KB, MB, GB)
46///
47/// ## Example
48/// ```
49/// # use pretty_bytes_typed::pretty_bytes;
50/// // No rounding
51/// let prettified = pretty_bytes(2_000_000, None);
52/// assert_eq!(prettified.to_string(), "2 MB");
53///
54/// // Round to 3 decimal places
55/// let prettified = pretty_bytes(3_564_234, Some(3));
56/// assert_eq!(prettified.to_string(), "3.564 MB");
57/// ```
58// Most likely, values will be too small to experience precision loss, and they will often be rounded anyway
59#[allow(clippy::cast_precision_loss)]
60pub fn pretty_bytes(num: u64, round_places: Option<u8>) -> PrettyBytes {
61    // Special handling for 0, because you can't use log10 on it
62    if num == 0 {
63        return PrettyBytes {
64            num: 0.,
65            suffix: ByteValues::B,
66        };
67    }
68
69    let exponent = std::cmp::min((num.ilog10() / 3) as usize, ByteValues::UNITS.len() - 1);
70
71    let mut num = num as f64 / 1000_f64.powi(exponent as i32);
72
73    if let Some(round_places) = round_places {
74        num = round_float(num, round_places);
75    }
76
77    let unit = ByteValues::UNITS[exponent];
78
79    PrettyBytes { num, suffix: unit }
80}
81
82/// Convert a byte value to a "prettified" version, but accepts negative numbers
83///
84/// Converts using base-10 byte suffixes (KB, MB, GB)
85///
86/// ## Example
87/// ```
88/// # use pretty_bytes_typed::pretty_bytes_signed;
89/// let prettified = pretty_bytes_signed(-2_000_000, None);
90/// assert_eq!(prettified.to_string(), "-2 MB");
91/// ```
92pub fn pretty_bytes_signed(num: i64, round_places: Option<u8>) -> PrettyBytes {
93    let is_negative = num.is_negative();
94    let num = num.unsigned_abs();
95
96    let mut pretty_bytes = pretty_bytes(num, round_places);
97
98    if is_negative {
99        pretty_bytes.num = -pretty_bytes.num;
100    }
101
102    pretty_bytes
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    #[allow(clippy::too_many_lines)]
111    fn test_pretty_bytes() {
112        // Test '0'
113        assert_eq!(
114            pretty_bytes(0, None),
115            PrettyBytes {
116                num: 0.,
117                suffix: ByteValues::B,
118            }
119        );
120
121        assert_eq!(
122            pretty_bytes(5_430, None),
123            PrettyBytes {
124                num: 5.43,
125                suffix: ByteValues::KB,
126            }
127        );
128
129        // Test all unit values
130        assert_eq!(
131            pretty_bytes(1000_u64.pow(0), None),
132            PrettyBytes {
133                num: 1.,
134                suffix: ByteValues::B,
135            }
136        );
137
138        assert_eq!(
139            pretty_bytes(1000_u64.pow(1), None),
140            PrettyBytes {
141                num: 1.,
142                suffix: ByteValues::KB,
143            }
144        );
145
146        assert_eq!(
147            pretty_bytes(1000_u64.pow(2), None),
148            PrettyBytes {
149                num: 1.,
150                suffix: ByteValues::MB,
151            }
152        );
153
154        assert_eq!(
155            pretty_bytes(1000_u64.pow(3), None),
156            PrettyBytes {
157                num: 1.,
158                suffix: ByteValues::GB,
159            }
160        );
161
162        assert_eq!(
163            pretty_bytes(1000_u64.pow(4), None),
164            PrettyBytes {
165                num: 1.,
166                suffix: ByteValues::TB,
167            }
168        );
169
170        assert_eq!(
171            pretty_bytes(1000_u64.pow(5), None),
172            PrettyBytes {
173                num: 1.,
174                suffix: ByteValues::PB,
175            }
176        );
177
178        assert_eq!(
179            pretty_bytes(1000_u64.pow(6), None),
180            PrettyBytes {
181                num: 1.,
182                suffix: ByteValues::EB,
183            }
184        );
185
186        // Test extra large values (near u64::MAX)
187        assert_eq!(
188            pretty_bytes(18_000_000_000_000_000_000, None),
189            PrettyBytes {
190                num: 18.,
191                suffix: ByteValues::EB,
192            }
193        );
194
195        // Various other tests
196        assert_eq!(
197            pretty_bytes(50060, None),
198            PrettyBytes {
199                num: 50.06,
200                suffix: ByteValues::KB,
201            }
202        );
203
204        assert_eq!(
205            pretty_bytes(736_532_432, None),
206            PrettyBytes {
207                num: 736.532_432,
208                suffix: ByteValues::MB,
209            }
210        );
211
212        // Test rounding
213        assert_eq!(
214            pretty_bytes(5003, Some(2)),
215            PrettyBytes {
216                num: 5.,
217                suffix: ByteValues::KB,
218            }
219        );
220
221        assert_eq!(
222            pretty_bytes(8_452_020, Some(2)),
223            PrettyBytes {
224                num: 8.45,
225                suffix: ByteValues::MB,
226            }
227        );
228
229        assert_eq!(
230            pretty_bytes(55_700, Some(0)),
231            PrettyBytes {
232                num: 56.,
233                suffix: ByteValues::KB,
234            }
235        );
236    }
237}