1use crate::errors::*;
2use std::fmt;
3use std::str::FromStr;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
7pub enum Prefix {
8 Nano,
10 Micro,
12 Milli,
14 One,
16 OneButBinary,
19 Kilo,
21 Kibi,
23 Mega,
25 Mebi,
27 Giga,
29 Gibi,
31 Tera,
33 Tebi,
35}
36
37const MUL: [f64; 13] = [
38 1e-9,
39 1e-6,
40 1e-3,
41 1.0,
42 1.0,
43 1e3,
44 1024.0,
45 1e6,
46 1024.0 * 1024.0,
47 1e9,
48 1024.0 * 1024.0 * 1024.0,
49 1e12,
50 1024.0 * 1024.0 * 1024.0 * 1024.0,
51];
52
53impl Prefix {
54 pub fn min_available() -> Self {
55 Self::Nano
56 }
57
58 pub fn max_available() -> Self {
59 Self::Tebi
60 }
61
62 pub fn max(self, other: Self) -> Self {
63 if other > self { other } else { self }
64 }
65
66 pub fn apply(self, value: f64) -> f64 {
67 value / MUL[self as usize]
68 }
69
70 pub fn eng(mut number: f64) -> Self {
71 if number == 0.0 {
72 Self::One
73 } else {
74 number = number.abs();
75 if number > 1.0 {
76 number = number.round();
77 } else {
78 let round_up_to = -(number.log10().ceil() as i32);
79 let m = 10f64.powi(round_up_to);
80 number = (number * m).round() / m;
81 }
82 match number.log10().div_euclid(3.) as i32 {
83 i32::MIN..=-3 => Prefix::Nano,
84 -2 => Prefix::Micro,
85 -1 => Prefix::Milli,
86 0 => Prefix::One,
87 1 => Prefix::Kilo,
88 2 => Prefix::Mega,
89 3 => Prefix::Giga,
90 4..=i32::MAX => Prefix::Tera,
91 }
92 }
93 }
94
95 pub fn eng_binary(number: f64) -> Self {
96 if number == 0.0 {
97 Self::One
98 } else {
99 match number.abs().round().log2().div_euclid(10.) as i32 {
100 i32::MIN..=0 => Prefix::OneButBinary,
101 1 => Prefix::Kibi,
102 2 => Prefix::Mebi,
103 3 => Prefix::Gibi,
104 4..=i32::MAX => Prefix::Tebi,
105 }
106 }
107 }
108
109 pub fn is_binary(&self) -> bool {
110 matches!(
111 self,
112 Self::OneButBinary | Self::Kibi | Self::Mebi | Self::Gibi | Self::Tebi
113 )
114 }
115}
116
117impl fmt::Display for Prefix {
118 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
119 f.write_str(match self {
120 Self::Nano => "n",
121 Self::Micro => "u",
122 Self::Milli => "m",
123 Self::One | Self::OneButBinary => "",
124 Self::Kilo => "K",
125 Self::Kibi => "Ki",
126 Self::Mega => "M",
127 Self::Mebi => "Mi",
128 Self::Giga => "G",
129 Self::Gibi => "Gi",
130 Self::Tera => "T",
131 Self::Tebi => "Ti",
132 })
133 }
134}
135
136impl FromStr for Prefix {
137 type Err = Error;
138
139 fn from_str(s: &str) -> Result<Self> {
140 match s {
141 "n" => Ok(Prefix::Nano),
142 "u" => Ok(Prefix::Micro),
143 "m" => Ok(Prefix::Milli),
144 "1" => Ok(Prefix::One),
145 "1i" => Ok(Prefix::OneButBinary),
146 "K" => Ok(Prefix::Kilo),
147 "Ki" => Ok(Prefix::Kibi),
148 "M" => Ok(Prefix::Mega),
149 "Mi" => Ok(Prefix::Mebi),
150 "G" => Ok(Prefix::Giga),
151 "Gi" => Ok(Prefix::Gibi),
152 "T" => Ok(Prefix::Tera),
153 "Ti" => Ok(Prefix::Tebi),
154 x => Err(Error::new(format!("Unknown prefix: '{x}'"))),
155 }
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn eng() {
165 assert_eq!(Prefix::eng(0.000_000_000_1), Prefix::Nano);
166 assert_eq!(Prefix::eng(0.000_000_001), Prefix::Nano);
167 assert_eq!(Prefix::eng(0.000_000_01), Prefix::Nano);
168 assert_eq!(Prefix::eng(0.000_000_1), Prefix::Nano);
169 assert_eq!(Prefix::eng(0.000_001), Prefix::Micro);
170 assert_eq!(Prefix::eng(0.000_01), Prefix::Micro);
171 assert_eq!(Prefix::eng(0.000_1), Prefix::Micro);
172 assert_eq!(Prefix::eng(0.001), Prefix::Milli);
173 assert_eq!(Prefix::eng(0.01), Prefix::Milli);
174 assert_eq!(Prefix::eng(0.1), Prefix::Milli);
175 assert_eq!(Prefix::eng(1.0), Prefix::One);
176 assert_eq!(Prefix::eng(10.0), Prefix::One);
177 assert_eq!(Prefix::eng(100.0), Prefix::One);
178 assert_eq!(Prefix::eng(1_000.0), Prefix::Kilo);
179 assert_eq!(Prefix::eng(10_000.0), Prefix::Kilo);
180 assert_eq!(Prefix::eng(100_000.0), Prefix::Kilo);
181 assert_eq!(Prefix::eng(1_000_000.0), Prefix::Mega);
182 assert_eq!(Prefix::eng(10_000_000.0), Prefix::Mega);
183 assert_eq!(Prefix::eng(100_000_000.0), Prefix::Mega);
184 assert_eq!(Prefix::eng(1_000_000_000.0), Prefix::Giga);
185 assert_eq!(Prefix::eng(10_000_000_000.0), Prefix::Giga);
186 assert_eq!(Prefix::eng(100_000_000_000.0), Prefix::Giga);
187 assert_eq!(Prefix::eng(1_000_000_000_000.0), Prefix::Tera);
188 assert_eq!(Prefix::eng(10_000_000_000_000.0), Prefix::Tera);
189 assert_eq!(Prefix::eng(100_000_000_000_000.0), Prefix::Tera);
190 assert_eq!(Prefix::eng(1_000_000_000_000_000.0), Prefix::Tera);
191 }
192
193 #[test]
194 fn eng_round() {
195 assert_eq!(Prefix::eng(0.000_000_000_09), Prefix::Nano);
196 assert_eq!(Prefix::eng(0.000_000_000_9), Prefix::Nano);
197 assert_eq!(Prefix::eng(0.000_000_009), Prefix::Nano);
198 assert_eq!(Prefix::eng(0.000_000_09), Prefix::Nano);
199 assert_eq!(Prefix::eng(0.000_000_9), Prefix::Micro);
200 assert_eq!(Prefix::eng(0.000_009), Prefix::Micro);
201 assert_eq!(Prefix::eng(0.000_09), Prefix::Micro);
202 assert_eq!(Prefix::eng(0.000_9), Prefix::Milli);
203 assert_eq!(Prefix::eng(0.009), Prefix::Milli);
204 assert_eq!(Prefix::eng(0.09), Prefix::Milli);
205 assert_eq!(Prefix::eng(0.9), Prefix::One);
206 assert_eq!(Prefix::eng(9.9), Prefix::One);
207 assert_eq!(Prefix::eng(99.9), Prefix::One);
208 assert_eq!(Prefix::eng(999.9), Prefix::Kilo);
209 assert_eq!(Prefix::eng(9_999.9), Prefix::Kilo);
210 assert_eq!(Prefix::eng(99_999.9), Prefix::Kilo);
211 assert_eq!(Prefix::eng(999_999.9), Prefix::Mega);
212 assert_eq!(Prefix::eng(9_999_999.9), Prefix::Mega);
213 assert_eq!(Prefix::eng(99_999_999.9), Prefix::Mega);
214 assert_eq!(Prefix::eng(999_999_999.9), Prefix::Giga);
215 assert_eq!(Prefix::eng(9_999_999_999.9), Prefix::Giga);
216 assert_eq!(Prefix::eng(99_999_999_999.9), Prefix::Giga);
217 assert_eq!(Prefix::eng(999_999_999_999.9), Prefix::Tera);
218 assert_eq!(Prefix::eng(9_999_999_999_999.9), Prefix::Tera);
219 assert_eq!(Prefix::eng(99_999_999_999_999.9), Prefix::Tera);
220 assert_eq!(Prefix::eng(999_999_999_999_999.9), Prefix::Tera);
221 }
222
223 #[test]
224 fn eng_binary() {
225 assert_eq!(Prefix::eng_binary(0.1), Prefix::OneButBinary);
226 assert_eq!(Prefix::eng_binary(1.0), Prefix::OneButBinary);
227 assert_eq!(Prefix::eng_binary((1 << 9) as f64), Prefix::OneButBinary);
228 assert_eq!(Prefix::eng_binary((1 << 10) as f64), Prefix::Kibi);
229 assert_eq!(Prefix::eng_binary((1 << 19) as f64), Prefix::Kibi);
230 assert_eq!(Prefix::eng_binary((1 << 29) as f64), Prefix::Mebi);
231 assert_eq!(Prefix::eng_binary((1 << 20) as f64), Prefix::Mebi);
232 assert_eq!(Prefix::eng_binary((1 << 30) as f64), Prefix::Gibi);
233 assert_eq!(Prefix::eng_binary((1_u64 << 39) as f64), Prefix::Gibi);
234 assert_eq!(Prefix::eng_binary((1_u64 << 40) as f64), Prefix::Tebi);
235 assert_eq!(Prefix::eng_binary((1_u64 << 49) as f64), Prefix::Tebi);
236 assert_eq!(Prefix::eng_binary((1_u64 << 50) as f64), Prefix::Tebi);
237 }
238
239 #[test]
240 fn eng_binary_round() {
241 assert_eq!(Prefix::eng_binary(0.9), Prefix::OneButBinary);
242 assert_eq!(
243 Prefix::eng_binary((1 << 9) as f64 - 0.1),
244 Prefix::OneButBinary
245 );
246 assert_eq!(Prefix::eng_binary((1 << 10) as f64 - 0.1), Prefix::Kibi);
247 assert_eq!(Prefix::eng_binary((1 << 19) as f64 - 0.1), Prefix::Kibi);
248 assert_eq!(Prefix::eng_binary((1 << 29) as f64 - 0.1), Prefix::Mebi);
249 assert_eq!(Prefix::eng_binary((1 << 20) as f64 - 0.1), Prefix::Mebi);
250 assert_eq!(Prefix::eng_binary((1 << 30) as f64 - 0.1), Prefix::Gibi);
251 assert_eq!(Prefix::eng_binary((1_u64 << 39) as f64 - 0.1), Prefix::Gibi);
252 assert_eq!(Prefix::eng_binary((1_u64 << 40) as f64 - 0.1), Prefix::Tebi);
253 assert_eq!(Prefix::eng_binary((1_u64 << 49) as f64 - 0.1), Prefix::Tebi);
254 assert_eq!(Prefix::eng_binary((1_u64 << 50) as f64 - 0.1), Prefix::Tebi);
255 }
256}