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