1use std::borrow::Cow;
2
3const NEGATIVE_SI_SCALE: &[Scale<'static>] = &[
4 Scale::new(1.0e-03, Cow::Borrowed("m")),
5 Scale::new(1.0e-06, Cow::Borrowed("μ")),
6 Scale::new(1.0e-09, Cow::Borrowed("n")),
7 Scale::new(1.0e-12, Cow::Borrowed("p")),
8 Scale::new(1.0e-15, Cow::Borrowed("f")),
9 Scale::new(1.0e-18, Cow::Borrowed("a")),
10 Scale::new(1.0e-21, Cow::Borrowed("z")),
11 Scale::new(1.0e-24, Cow::Borrowed("y")),
12 Scale::new(1.0e-27, Cow::Borrowed("r")),
13 Scale::new(1.0e-30, Cow::Borrowed("q")),
14];
15const POSITIVE_SI_SCALE: &[Scale<'static>] = &[
16 Scale::new(1.0e+03, Cow::Borrowed("k")),
17 Scale::new(1.0e+06, Cow::Borrowed("M")),
18 Scale::new(1.0e+09, Cow::Borrowed("G")),
19 Scale::new(1.0e+12, Cow::Borrowed("T")),
20 Scale::new(1.0e+15, Cow::Borrowed("P")),
21 Scale::new(1.0e+18, Cow::Borrowed("E")),
22 Scale::new(1.0e+21, Cow::Borrowed("Z")),
23 Scale::new(1.0e+24, Cow::Borrowed("Y")),
24 Scale::new(1.0e+27, Cow::Borrowed("R")),
25 Scale::new(1.0e+30, Cow::Borrowed("Q")),
26];
27pub const SI_SCALE: Scales<'static> = Scales::new(NEGATIVE_SI_SCALE, POSITIVE_SI_SCALE);
28
29const POSITIVE_BINARY_SCALE: &[Scale<'static>] = &[
30 Scale::new(1024.0, Cow::Borrowed("ki")),
31 Scale::new(1048576.0, Cow::Borrowed("Mi")),
32 Scale::new(1073741824.0, Cow::Borrowed("Gi")),
33 Scale::new(1099511627776.0, Cow::Borrowed("Ti")),
34 Scale::new(1125899906842624.0, Cow::Borrowed("Pi")),
35 Scale::new(1152921504606846976.0, Cow::Borrowed("Ei")),
36 Scale::new(1180591620717411303424.0, Cow::Borrowed("Zi")),
37 Scale::new(1208925819614629174706176.0, Cow::Borrowed("Yi")),
38 Scale::new(1237940039285380274899124224.0, Cow::Borrowed("Ri")),
39 Scale::new(1267650600228229401496703205376.0, Cow::Borrowed("Qi")),
40];
41pub const BINARY_SCALE: Scales<'static> = Scales::new(&[], POSITIVE_BINARY_SCALE);
42
43#[derive(Debug, Clone, PartialEq)]
44pub struct Scale<'a> {
45 factor: f64,
46 prefix: Cow<'a, str>,
47}
48
49impl<'a> Scale<'a> {
50 #[inline]
51 pub const fn new(factor: f64, prefix: Cow<'a, str>) -> Self {
52 Self { factor, prefix }
53 }
54}
55
56#[derive(Clone, Debug, PartialEq)]
57pub struct Scales<'a> {
58 negatives: &'a [Scale<'a>],
59 positives: &'a [Scale<'a>],
60}
61
62impl<'a> Scales<'a> {
63 pub const fn empty() -> Self {
64 Self::new(&[], &[])
65 }
66
67 pub const fn new(negatives: &'a [Scale<'a>], positives: &'a [Scale<'a>]) -> Self {
68 Self {
69 negatives,
70 positives,
71 }
72 }
73
74 fn get_negative_scale(&'a self, absolute: f64) -> Option<&'a Scale<'a>> {
75 for current in self.negatives.iter() {
76 if absolute >= current.factor {
77 return Some(current);
78 }
79 }
80 self.negatives.last()
81 }
82
83 fn get_positive_scale(&'a self, absolute: f64) -> Option<&'a Scale<'a>> {
84 let mut previous = None;
85 for current in self.positives.iter() {
86 if absolute < current.factor {
87 return previous;
88 }
89 previous = Some(current);
90 }
91 previous
92 }
93
94 pub fn get_scale(&'a self, value: f64) -> Option<&'a Scale<'a>> {
95 let absolute = f64::abs(value);
96 if absolute < 1.0 {
97 self.get_negative_scale(absolute)
98 } else {
99 self.get_positive_scale(absolute)
100 }
101 }
102
103 pub fn into_scaled(&'a self, options: &'a Options<'a>, value: f64) -> ScaledValue<'a> {
104 if let Some(scale) = self.get_scale(value) {
105 ScaledValue {
106 value: value / scale.factor,
107 scale: Some(scale),
108 options,
109 }
110 } else {
111 ScaledValue {
112 value,
113 scale: None,
114 options,
115 }
116 }
117 }
118}
119
120#[derive(Clone, Debug)]
121pub struct ScaledValue<'a> {
122 value: f64,
123 scale: Option<&'a Scale<'a>>,
124 options: &'a Options<'a>,
125}
126
127impl std::fmt::Display for ScaledValue<'_> {
128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129 if self.options.force_sign {
130 write!(f, "{:+.width$}", self.value, width = self.options.decimals)?;
131 } else {
132 write!(f, "{:.width$}", self.value, width = self.options.decimals)?;
133 }
134 if self.scale.is_some() || self.options.unit.is_some() {
135 f.write_str(self.options.separator.as_ref())?;
136 }
137 if let Some(scale) = self.scale {
138 f.write_str(scale.prefix.as_ref())?;
139 }
140 if let Some(ref unit) = self.options.unit {
141 f.write_str(unit.as_ref())?;
142 }
143 Ok(())
144 }
145}
146
147#[derive(Clone, Debug, PartialEq, Eq)]
149pub struct Options<'a> {
150 decimals: usize,
151 separator: Cow<'a, str>,
152 unit: Option<Cow<'a, str>>,
153 force_sign: bool,
154}
155
156impl Default for Options<'_> {
157 fn default() -> Self {
158 Self {
159 decimals: 2,
160 separator: Cow::Borrowed(" "),
161 unit: None,
162 force_sign: false,
163 }
164 }
165}
166
167impl<'a> Options<'a> {
168 pub const fn new(decimals: usize, separator: Cow<'a, str>, unit: Option<Cow<'a, str>>) -> Self {
169 Self {
170 decimals,
171 separator,
172 unit,
173 force_sign: false,
174 }
175 }
176
177 #[inline]
179 pub fn set_force_sign(&mut self, force_sign: bool) {
180 self.force_sign = force_sign;
181 }
182
183 pub const fn with_force_sign(mut self, force_sign: bool) -> Self {
185 self.force_sign = force_sign;
186 self
187 }
188
189 #[inline]
191 pub fn set_decimals(&mut self, decimals: usize) {
192 self.decimals = decimals;
193 }
194
195 pub const fn with_decimals(mut self, decimals: usize) -> Self {
197 self.decimals = decimals;
198 self
199 }
200
201 #[inline]
203 pub fn set_unit<U: Into<Cow<'a, str>>>(&mut self, unit: U) {
204 self.unit = Some(unit.into());
205 }
206
207 pub fn with_unit<U: Into<Cow<'a, str>>>(mut self, unit: U) -> Self {
209 self.set_unit(unit);
210 self
211 }
212
213 #[inline]
215 pub fn set_separator<U: Into<Cow<'a, str>>>(&mut self, separator: U) {
216 self.separator = separator.into();
217 }
218
219 pub fn with_separator<U: Into<Cow<'a, str>>>(mut self, separator: U) -> Self {
221 self.set_separator(separator);
222 self
223 }
224}
225
226#[derive(Clone, Debug, PartialEq)]
229pub struct Formatter<'a> {
230 scales: Scales<'a>,
231 options: Options<'a>,
232}
233
234impl<'a> Formatter<'a> {
235 #[inline]
237 pub fn new(scales: Scales<'a>, options: Options<'a>) -> Self {
238 Self { scales, options }
239 }
240
241 #[inline]
243 pub fn set_force_sign(&mut self, force_sign: bool) {
244 self.options.set_force_sign(force_sign);
245 }
246
247 pub fn with_force_sign(mut self, force_sign: bool) -> Self {
249 self.options.set_force_sign(force_sign);
250 self
251 }
252
253 #[inline]
255 pub fn set_decimals(&mut self, decimals: usize) {
256 self.options.decimals = decimals;
257 }
258
259 pub fn with_decimals(mut self, decimals: usize) -> Self {
261 self.options.set_decimals(decimals);
262 self
263 }
264
265 #[inline]
267 pub fn set_unit<U: Into<Cow<'a, str>>>(&mut self, unit: U) {
268 self.options.unit = Some(unit.into());
269 }
270
271 pub fn with_unit<U: Into<Cow<'a, str>>>(mut self, unit: U) -> Self {
273 self.options.set_unit(unit);
274 self
275 }
276
277 #[inline]
279 pub fn set_separator<U: Into<Cow<'a, str>>>(&mut self, separator: U) {
280 self.options.separator = separator.into();
281 }
282
283 pub fn with_separator<U: Into<Cow<'a, str>>>(mut self, separator: U) -> Self {
285 self.options.set_separator(separator);
286 self
287 }
288
289 #[inline]
291 pub fn format(&'a self, value: f64) -> ScaledValue<'a> {
292 self.scales.into_scaled(&self.options, value)
293 }
294}
295
296impl Formatter<'static> {
297 pub fn si() -> Self {
315 Formatter {
316 scales: SI_SCALE,
317 options: Options::<'static>::default(),
318 }
319 }
320
321 pub fn binary() -> Self {
331 Formatter {
332 scales: BINARY_SCALE,
333 options: Options::<'static>::default(),
334 }
335 }
336
337 pub fn empty() -> Self {
351 Formatter {
352 scales: Scales::empty(),
353 options: Options::<'static>::default(),
354 }
355 }
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361
362 #[test]
363 fn getting_scale() {
364 assert!(SI_SCALE.get_scale(1.0).is_none());
365 assert_eq!(SI_SCALE.get_scale(1000.0).unwrap().prefix, "k");
366 assert_eq!(SI_SCALE.get_scale(0.10).unwrap().prefix, "m");
367 }
368
369 #[test_case::test_case(0.005, "5.000 m"; "should small number")]
370 #[test_case::test_case(100.0, "100.000"; "should number")]
371 #[test_case::test_case(5_432_100.0, "5.432 M"; "should format big number")]
372 fn format_si_values_with_decimals(value: f64, expected: &'static str) {
373 let formatter = Formatter::si().with_decimals(3);
374 let result = format!("{}", formatter.format(value));
375 assert_eq!(result, expected);
376 }
377
378 #[test_case::test_case(0.005, "5.00š¦m"; "should small number")]
379 #[test_case::test_case(100.0, "100.00"; "should number")]
380 #[test_case::test_case(5_432_100.0, "5.43š¦M"; "should format big number")]
381 fn format_si_values_with_separator(value: f64, expected: &'static str) {
382 let formatter = Formatter::si().with_separator("š¦");
383 let result = format!("{}", formatter.format(value));
384 assert_eq!(result, expected);
385 }
386
387 #[test_case::test_case(0.005, "5.00 m"; "should small number")]
388 #[test_case::test_case(100.0, "100.00"; "should number")]
389 #[test_case::test_case(5_432_100.0, "5.43 M"; "should format big number")]
390 fn format_si_values_without_unit(value: f64, expected: &'static str) {
391 let formatter = Formatter::si();
392 let result = format!("{}", formatter.format(value));
393 assert_eq!(result, expected);
394 }
395
396 #[test_case::test_case(0.005, "5.00 mg"; "should small number")]
397 #[test_case::test_case(100.0, "100.00 g"; "should number")]
398 #[test_case::test_case(5_432_100.0, "5.43 Mg"; "should format big number")]
399 fn format_si_values_with_unit(value: f64, expected: &'static str) {
400 let formatter = Formatter::si().with_unit("g");
401 let result = format!("{}", formatter.format(value));
402 assert_eq!(result, expected);
403 }
404
405 #[test_case::test_case(100.0, "100.00"; "should number")]
406 #[test_case::test_case(4096.0, "4.00 ki"; "should format kilo number")]
407 #[test_case::test_case(4194304.0, "4.00 Mi"; "should format mega number")]
408 fn format_binary_values_without_unit(value: f64, expected: &'static str) {
409 let formatter = Formatter::binary();
410 let result = format!("{}", formatter.format(value));
411 assert_eq!(result, expected);
412 }
413
414 #[test_case::test_case(100.0, "100.00 B"; "should number")]
415 #[test_case::test_case(4096.0, "4.00 kiB"; "should format kilo number")]
416 #[test_case::test_case(4194304.0, "4.00 MiB"; "should format mega number")]
417 fn format_binary_values_with_unit(value: f64, expected: &'static str) {
418 let formatter = Formatter::binary().with_unit("B");
419 let result = format!("{}", formatter.format(value));
420 assert_eq!(result, expected);
421 }
422
423 #[test]
424 fn format_with_sign() {
425 let scales: Scales = Scales::new(&[], &[]);
426 let options = Options::default().with_force_sign(true);
427 let formatter = Formatter::new(scales, options);
428 assert_eq!(format!("{}", formatter.format(-1.0)), "-1.00");
429 assert_eq!(format!("{}", formatter.format(1.0)), "+1.00");
430 }
431
432 #[test]
433 fn format_with_non_static() {
434 let negatives = vec![Scale::new(0.000_001, String::from("x").into())];
435 let positives = vec![Scale::new(1_000.0, String::from("k").into())];
436 let scales: Scales = Scales::new(&negatives, &positives);
437 let options = Options::default()
438 .with_unit("š¦")
439 .with_separator("")
440 .with_decimals(1);
441 let formatter = Formatter::new(scales, options);
442 assert_eq!(format!("{}", formatter.format(1_234.567)), "1.2kš¦");
443 assert_eq!(
444 format!("{}", formatter.format(0.000_012_345_678)),
445 "12.3xš¦"
446 );
447 }
448}