rust_functions/
lib.rs

1pub mod number_formatting {
2    const DEFAULT_DECIMAL_VALUE: usize = 3;
3
4    fn group_integer(n: u128, k: usize) -> String {
5        let mut s = n.to_string();
6        if s == "0" {
7            return "0".repeat(k);
8        }
9        let pad = (k - (s.len() % k)) % k;
10        if pad > 0 {
11            s = "0".repeat(pad) + &s;
12        }
13        s.as_bytes()
14            .chunks(k)
15            .map(|c| std::str::from_utf8(c).unwrap())
16            .collect::<Vec<_>>()
17            .join("_")
18    }
19
20    fn group_fractional(frac: &str, k: usize) -> String {
21        let mut f = if frac.is_empty() { "0".to_string() } else { frac.to_string() };
22        let pad = (k - (f.len() % k)) % k;
23        if pad > 0 {
24            f.push_str(&"0".repeat(pad));
25        }
26        f.as_bytes()
27            .chunks(k)
28            .map(|c| std::str::from_utf8(c).unwrap())
29            .collect::<Vec<_>>()
30            .join("_")
31    }
32
33    fn format_number_internal(x: &str, k: usize) -> String {
34        let mut s = x.trim().to_string();
35
36        let mut negative = false;
37        if s.starts_with('+') || s.starts_with('-') {
38            negative = s.starts_with('-');
39            s = s[1..].to_string();
40        }
41
42        if s.contains('.') {
43            let parts: Vec<&str> = s.splitn(2, '.').collect();
44            let int_part = if parts[0].is_empty() { "0" } else { parts[0] };
45            let frac_part = if parts.len() > 1 { parts[1] } else { "0" };
46
47            let int_val: u128 = int_part.parse().unwrap();
48            let int_fmt = group_integer(int_val, k);
49            let frac_fmt = group_fractional(frac_part, k);
50
51            let mut out = format!("{}_decimal_point_{}", int_fmt, frac_fmt);
52            if negative {
53                out = format!("negative_{}", out);
54            }
55            return out;
56        }
57
58        if s.is_empty() {
59            s = "0".to_string();
60        }
61        let int_val: u128 = s.parse().unwrap();
62        let mut out = group_integer(int_val, k);
63        if negative {
64            out = format!("negative_{}", out);
65        }
66        out
67    }
68
69    /// Trait to accept either `&str` or `usize` as group size
70    pub trait IntoGroupSize {
71        fn into_group_size(self) -> usize;
72    }
73
74    impl IntoGroupSize for &str {
75        fn into_group_size(self) -> usize {
76            if self.is_empty() {
77                return DEFAULT_DECIMAL_VALUE;
78            }
79            let k: usize = self.parse().expect("group_size must be a positive integer");
80            if k == 0 {
81                panic!("group_size must be positive");
82            }
83            k
84        }
85    }
86
87    impl IntoGroupSize for usize {
88        fn into_group_size(self) -> usize {
89            if self == 0 {
90                panic!("group_size must be positive");
91            }
92            self
93        }
94    }
95
96    /// Now one function works for both `&str` and `usize`
97    pub fn format_number<T: IntoGroupSize>(x: &str, group_size: T) -> String {
98        let k = group_size.into_group_size();
99        format_number_internal(x, k)
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::number_formatting::format_number;
106
107    #[test]
108    fn test_format_number_with_str() {
109        assert_eq!(format_number("12345", ""), "012_345"); // default group size = 3
110        assert_eq!(format_number("12345", "4"), "0001_2345");
111        assert_eq!(format_number("1.23456", "4"), "0001_decimal_point_2345_6000");
112        assert_eq!(format_number("-1234", ""), "negative_001_234");
113    }
114
115    #[test]
116    fn test_format_number_with_usize() {
117        assert_eq!(format_number("12345", 3), "012_345"); // explicit usize 3
118        assert_eq!(format_number("12345", 4), "0001_2345");
119        assert_eq!(format_number("1.23456", 4), "0001_decimal_point_2345_6000");
120        assert_eq!(format_number("-1234", 3), "negative_001_234");
121    }
122
123    #[test]
124    #[should_panic(expected = "group_size must be positive")]
125    fn test_format_number_with_zero_usize_panics() {
126        format_number("12345", 0);
127    }
128
129    #[test]
130    #[should_panic(expected = "group_size must be positive")]
131    fn test_format_number_with_zero_str_panics() {
132        format_number("12345", "0");
133    }
134}