pretty_bytes_typed/
binary.rs

1#![allow(clippy::module_name_repetitions)]
2
3use crate::util::round_float;
4
5/// Struct that represents prettified byte values (base-2)
6#[derive(Debug, PartialEq, Clone)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8#[must_use]
9pub struct PrettyBytesBinary {
10    num: f64,
11    suffix: ByteValuesBinary,
12}
13
14impl std::fmt::Display for PrettyBytesBinary {
15    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16        write!(f, "{} {:?}", self.num, self.suffix)
17    }
18}
19
20#[derive(Clone, Copy, Debug, PartialEq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22enum ByteValuesBinary {
23    B,
24    KiB,
25    MiB,
26    GiB,
27    TiB,
28    PiB,
29    EiB,
30}
31
32impl ByteValuesBinary {
33    // EiB is the max that can be represented with a u64
34    const UNITS: [Self; 7] = [
35        Self::B,
36        Self::KiB,
37        Self::MiB,
38        Self::GiB,
39        Self::TiB,
40        Self::PiB,
41        Self::EiB,
42    ];
43}
44
45/// Convert a byte value to a "prettified" version
46///
47/// Converts using base-2 byte suffixes (KiB, MiB, GiB)
48///
49/// ## Example
50/// ```
51/// # use pretty_bytes_typed::pretty_bytes_binary;
52/// // No rounding
53/// let prettified = pretty_bytes_binary(1_048_576, None);
54/// assert_eq!(prettified.to_string(), "1 MiB");
55///
56/// // Round to 2 decimal places
57/// let prettified = pretty_bytes_binary(3_195_498, Some(2));
58/// assert_eq!(prettified.to_string(), "3.05 MiB");
59/// ```
60// Most likely, values will be too small to experience precision loss, and they will often be rounded anyway
61#[allow(clippy::cast_precision_loss)]
62pub fn pretty_bytes_binary(num: u64, round_places: Option<u8>) -> PrettyBytesBinary {
63    // Special handling for 0, because you can't use log on it
64    if num == 0 {
65        return PrettyBytesBinary {
66            num: 0.,
67            suffix: ByteValuesBinary::B,
68        };
69    }
70
71    let exponent = std::cmp::min(num.ilog(1024) as usize, ByteValuesBinary::UNITS.len() - 1);
72
73    let mut num = num as f64 / 1024_f64.powi(exponent as i32);
74
75    if let Some(round_places) = round_places {
76        num = round_float(num, round_places);
77    }
78
79    let unit = ByteValuesBinary::UNITS[exponent];
80
81    PrettyBytesBinary { num, suffix: unit }
82}
83
84/// Convert a byte value to a "prettified" version, but accepts negative numbers
85///
86/// Converts using base-2 byte suffixes (KiB, MiB, GiB)
87///
88/// ## Example
89/// ```
90/// # use pretty_bytes_typed::pretty_bytes_signed_binary;
91/// let prettified = pretty_bytes_signed_binary(-1_048_576, None);
92/// assert_eq!(prettified.to_string(), "-1 MiB");
93/// ```
94pub fn pretty_bytes_signed_binary(num: i64, round_places: Option<u8>) -> PrettyBytesBinary {
95    let is_negative = num.is_negative();
96    let num = num.unsigned_abs();
97
98    let mut pretty_bytes = pretty_bytes_binary(num, round_places);
99
100    if is_negative {
101        pretty_bytes.num = -pretty_bytes.num;
102    }
103
104    pretty_bytes
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    #[allow(clippy::too_many_lines)]
113    fn test_pretty_bytes_binary() {
114        // Test '0'
115        assert_eq!(
116            pretty_bytes_binary(0, None),
117            PrettyBytesBinary {
118                num: 0.,
119                suffix: ByteValuesBinary::B,
120            }
121        );
122
123        // Test all unit values
124        assert_eq!(
125            pretty_bytes_binary(1024_u64.pow(0), None),
126            PrettyBytesBinary {
127                num: 1.,
128                suffix: ByteValuesBinary::B,
129            }
130        );
131
132        assert_eq!(
133            pretty_bytes_binary(1024_u64.pow(1), None),
134            PrettyBytesBinary {
135                num: 1.,
136                suffix: ByteValuesBinary::KiB,
137            }
138        );
139
140        assert_eq!(
141            pretty_bytes_binary(1024_u64.pow(2), None),
142            PrettyBytesBinary {
143                num: 1.,
144                suffix: ByteValuesBinary::MiB,
145            }
146        );
147
148        assert_eq!(
149            pretty_bytes_binary(1024_u64.pow(3), None),
150            PrettyBytesBinary {
151                num: 1.,
152                suffix: ByteValuesBinary::GiB,
153            }
154        );
155
156        assert_eq!(
157            pretty_bytes_binary(1024_u64.pow(4), None),
158            PrettyBytesBinary {
159                num: 1.,
160                suffix: ByteValuesBinary::TiB,
161            }
162        );
163
164        assert_eq!(
165            pretty_bytes_binary(1024_u64.pow(5), None),
166            PrettyBytesBinary {
167                num: 1.,
168                suffix: ByteValuesBinary::PiB,
169            }
170        );
171
172        assert_eq!(
173            pretty_bytes_binary(1024_u64.pow(6), None),
174            PrettyBytesBinary {
175                num: 1.,
176                suffix: ByteValuesBinary::EiB,
177            }
178        );
179
180        // Test rounding
181        assert_eq!(
182            pretty_bytes_binary(5014, Some(2)),
183            PrettyBytesBinary {
184                num: 4.9,
185                suffix: ByteValuesBinary::KiB,
186            }
187        );
188    }
189}