human_number/
lib.rs

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/// Set of options used for formating numbers.
148#[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    /// Forces the sign to be displayed.
178    #[inline]
179    pub fn set_force_sign(&mut self, force_sign: bool) {
180        self.force_sign = force_sign;
181    }
182
183    /// Forces the sign to be displayed.
184    pub const fn with_force_sign(mut self, force_sign: bool) -> Self {
185        self.force_sign = force_sign;
186        self
187    }
188
189    /// Sets the number of decimals to display.
190    #[inline]
191    pub fn set_decimals(&mut self, decimals: usize) {
192        self.decimals = decimals;
193    }
194
195    /// Sets the number of decimals to display.
196    pub const fn with_decimals(mut self, decimals: usize) -> Self {
197        self.decimals = decimals;
198        self
199    }
200
201    /// Sets the expected unit, like `B` for bytes or `g` for grams.
202    #[inline]
203    pub fn set_unit<U: Into<Cow<'a, str>>>(&mut self, unit: U) {
204        self.unit = Some(unit.into());
205    }
206
207    /// Sets the expected unit, like `B` for bytes or `g` for grams.
208    pub fn with_unit<U: Into<Cow<'a, str>>>(mut self, unit: U) -> Self {
209        self.set_unit(unit);
210        self
211    }
212
213    /// Sets the separator between the number and the preffix.
214    #[inline]
215    pub fn set_separator<U: Into<Cow<'a, str>>>(&mut self, separator: U) {
216        self.separator = separator.into();
217    }
218
219    /// Sets the separator between the number and the preffix.
220    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/// Structure containing options and scales used to format numbers
227/// with the right scale preffix, separators and units.
228#[derive(Clone, Debug, PartialEq)]
229pub struct Formatter<'a> {
230    scales: Scales<'a>,
231    options: Options<'a>,
232}
233
234impl<'a> Formatter<'a> {
235    /// Create a formatter with a given scale set and some options
236    #[inline]
237    pub fn new(scales: Scales<'a>, options: Options<'a>) -> Self {
238        Self { scales, options }
239    }
240
241    /// Forces the sign to be displayed.
242    #[inline]
243    pub fn set_force_sign(&mut self, force_sign: bool) {
244        self.options.set_force_sign(force_sign);
245    }
246
247    /// Forces the sign to be displayed.
248    pub fn with_force_sign(mut self, force_sign: bool) -> Self {
249        self.options.set_force_sign(force_sign);
250        self
251    }
252
253    /// Sets the number of decimals to display.
254    #[inline]
255    pub fn set_decimals(&mut self, decimals: usize) {
256        self.options.decimals = decimals;
257    }
258
259    /// Sets the number of decimals to display.
260    pub fn with_decimals(mut self, decimals: usize) -> Self {
261        self.options.set_decimals(decimals);
262        self
263    }
264
265    /// Sets the expected unit, like `B` for bytes or `g` for grams.
266    #[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    /// Sets the expected unit, like `B` for bytes or `g` for grams.
272    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    /// Sets the separator between the number and the preffix.
278    #[inline]
279    pub fn set_separator<U: Into<Cow<'a, str>>>(&mut self, separator: U) {
280        self.options.separator = separator.into();
281    }
282
283    /// Sets the separator between the number and the preffix.
284    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    /// Formats a number and returns a scaled value that can be displayed.
290    #[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    /// Formatter that uses the SI format style
298    ///
299    /// ```rust
300    /// use human_number::Formatter;
301    ///
302    /// let formatter = Formatter::si();
303    /// let result = format!("{}", formatter.format(4_234.0));
304    /// assert_eq!(result, "4.23 k");
305    /// let result = format!("{}", formatter.format(0.012_34));
306    /// assert_eq!(result, "12.34 m");
307    ///
308    /// let formatter = Formatter::si().with_unit("g").with_separator("").with_decimals(1);
309    /// let result = format!("{}", formatter.format(4_234.0));
310    /// assert_eq!(result, "4.2kg");
311    /// let result = format!("{}", formatter.format(0.012_34));
312    /// assert_eq!(result, "12.3mg");
313    /// ```
314    pub fn si() -> Self {
315        Formatter {
316            scales: SI_SCALE,
317            options: Options::<'static>::default(),
318        }
319    }
320
321    /// Formatter that uses the binary format style
322    ///
323    /// ```rust
324    /// use human_number::Formatter;
325    ///
326    /// let formatter = Formatter::binary().with_unit("B");
327    /// let result = format!("{}", formatter.format(4_320_133.0));
328    /// assert_eq!(result, "4.12 MiB");
329    /// ```
330    pub fn binary() -> Self {
331        Formatter {
332            scales: BINARY_SCALE,
333            options: Options::<'static>::default(),
334        }
335    }
336
337    /// Formatter that doesn't use a scale
338    ///
339    /// ```rust
340    /// use human_number::Formatter;
341    ///
342    /// let formatter = Formatter::empty();
343    /// let result = format!("{}", formatter.format(25_000.0));
344    /// assert_eq!(result, "25000.00");
345    ///
346    /// let formatter = Formatter::empty().with_unit("%");
347    /// let result = format!("{}", formatter.format(25.0));
348    /// assert_eq!(result, "25.00 %");
349    /// ```
350    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}