Skip to main content

common/
display.rs

1//! Display and formatting utilities.
2
3/// Format a number for display with SI suffixes and appropriate precision.
4///
5/// Large numbers are displayed with K/M/B suffixes, while smaller numbers
6/// use appropriate decimal places based on magnitude.
7///
8/// # Examples
9///
10/// ```
11/// use common::display::format_number;
12///
13/// assert_eq!(format_number(1234567.0), "1.23M");
14/// assert_eq!(format_number(1500.0), "1.50K");
15/// assert_eq!(format_number(42.0), "42");
16/// assert_eq!(format_number(0.5), "0.50");
17/// assert_eq!(format_number(0.005), "0.0050");
18/// ```
19pub fn format_number(value: f64) -> String {
20    let abs = value.abs();
21
22    // Determine appropriate precision based on magnitude
23    let (formatted, suffix) = if abs >= 1_000_000_000.0 {
24        (value / 1_000_000_000.0, "B")
25    } else if abs >= 1_000_000.0 {
26        (value / 1_000_000.0, "M")
27    } else if abs >= 1_000.0 {
28        (value / 1_000.0, "K")
29    } else {
30        (value, "")
31    };
32
33    // Format with appropriate decimal places
34    if suffix.is_empty() {
35        if abs == 0.0 {
36            "0".to_string()
37        } else if abs < 0.01 {
38            format!("{:.4}", formatted)
39        } else if abs < 1.0 {
40            format!("{:.2}", formatted)
41        } else if formatted.fract() == 0.0 {
42            format!("{:.0}", formatted)
43        } else {
44            format!("{:.2}", formatted)
45        }
46    } else {
47        format!("{:.2}{}", formatted, suffix)
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn should_format_billions() {
57        assert_eq!(format_number(1_234_567_890.0), "1.23B");
58        assert_eq!(format_number(5_000_000_000.0), "5.00B");
59    }
60
61    #[test]
62    fn should_format_millions() {
63        assert_eq!(format_number(1_234_567.0), "1.23M");
64        assert_eq!(format_number(5_500_000.0), "5.50M");
65    }
66
67    #[test]
68    fn should_format_thousands() {
69        assert_eq!(format_number(1_234.0), "1.23K");
70        assert_eq!(format_number(52_647.0), "52.65K");
71    }
72
73    #[test]
74    fn should_format_whole_numbers() {
75        assert_eq!(format_number(42.0), "42");
76        assert_eq!(format_number(100.0), "100");
77    }
78
79    #[test]
80    fn should_format_decimals() {
81        assert_eq!(format_number(42.5), "42.50");
82        assert_eq!(format_number(0.75), "0.75");
83    }
84
85    #[test]
86    fn should_format_small_decimals() {
87        assert_eq!(format_number(0.005), "0.0050");
88        assert_eq!(format_number(0.0001), "0.0001");
89    }
90
91    #[test]
92    fn should_handle_zero() {
93        assert_eq!(format_number(0.0), "0");
94    }
95
96    #[test]
97    fn should_handle_negative_numbers() {
98        assert_eq!(format_number(-1_500.0), "-1.50K");
99        assert_eq!(format_number(-42.5), "-42.50");
100    }
101}