1pub struct NumberHelper;
3
4impl NumberHelper {
5 pub fn filesize_format(bytes: u64) -> String {
7 const UNITS: &[&str] = &["o", "Ko", "Mo", "Go", "To"];
8 let mut size = bytes as f64;
9 let mut unit_idx = 0;
10
11 while size >= 1024.0 && unit_idx < UNITS.len() - 1 {
12 size /= 1024.0;
13 unit_idx += 1;
14 }
15
16 if unit_idx == 0 {
17 format!("{} {}", bytes, UNITS[0])
18 } else {
19 format!("{:.1} {}", size, UNITS[unit_idx])
20 }
21 }
22
23 pub fn format_number(value: i64, separator: Option<&str>) -> String {
26 let separator = separator.unwrap_or(" ");
27 let negative = value < 0;
28 let s = value.unsigned_abs().to_string();
29 let chars: Vec<char> = s.chars().collect();
30 let mut result = String::new();
31
32 for (i, c) in chars.iter().enumerate() {
33 if i > 0 && (chars.len() - i) % 3 == 0 {
34 result.push_str(separator);
35 }
36 result.push(*c);
37 }
38
39 if negative {
40 format!("-{}", result)
41 } else {
42 result
43 }
44 }
45
46 pub fn format_decimal(value: f64, decimals: usize, separator: Option<&str>) -> String {
48 let integer_part = value.trunc() as i64;
49 let formatted_int = Self::format_number(integer_part, separator);
50
51 if decimals == 0 {
52 return formatted_int;
53 }
54
55 let decimal_part = (value.fract().abs() * 10_f64.powi(decimals as i32)).round() as u64;
56 let decimal_str = format!("{:0>width$}", decimal_part, width = decimals);
57
58 format!("{},{}", formatted_int, decimal_str)
59 }
60
61 pub fn ordinal(value: u64) -> String {
63 if value == 1 {
64 "1er".to_string()
65 } else {
66 format!("{}e", value)
67 }
68 }
69
70 pub fn percentage(value: f64, decimals: usize) -> String {
73 format!("{:.prec$}%", value * 100.0, prec = decimals)
74 }
75
76 pub fn clamp<T: PartialOrd>(value: T, min: T, max: T) -> T {
78 if value < min {
79 min
80 } else if value > max {
81 max
82 } else {
83 value
84 }
85 }
86
87 pub fn duration_format(total_seconds: u64) -> String {
90 let hours = total_seconds / 3600;
91 let minutes = (total_seconds % 3600) / 60;
92 let seconds = total_seconds % 60;
93
94 let mut parts = Vec::new();
95
96 if hours > 0 {
97 parts.push(format!("{}h", hours));
98 }
99 if minutes > 0 {
100 parts.push(format!("{}min", minutes));
101 }
102 if seconds > 0 || parts.is_empty() {
103 parts.push(format!("{}s", seconds));
104 }
105
106 parts.join(" ")
107 }
108
109 pub fn round(value: f64, decimals: u32) -> f64 {
111 let factor = 10_f64.powi(decimals as i32);
112 (value * factor).round() / factor
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn test_filesize_format() {
122 assert_eq!(NumberHelper::filesize_format(0), "0 o");
123 assert_eq!(NumberHelper::filesize_format(512), "512 o");
124 assert_eq!(NumberHelper::filesize_format(1024), "1.0 Ko");
125 assert_eq!(NumberHelper::filesize_format(1536), "1.5 Ko");
126 assert_eq!(NumberHelper::filesize_format(1048576), "1.0 Mo");
127 assert_eq!(NumberHelper::filesize_format(1073741824), "1.0 Go");
128 }
129
130 #[test]
131 fn test_format_number() {
132 assert_eq!(NumberHelper::format_number(1234567, None), "1 234 567");
133 assert_eq!(NumberHelper::format_number(42, None), "42");
134 assert_eq!(NumberHelper::format_number(1000, Some(",")), "1,000");
135 assert_eq!(NumberHelper::format_number(-1234, None), "-1 234");
136 assert_eq!(NumberHelper::format_number(0, None), "0");
137 }
138
139 #[test]
140 fn test_format_decimal() {
141 assert_eq!(NumberHelper::format_decimal(1234.56, 2, None), "1 234,56");
142 assert_eq!(NumberHelper::format_decimal(0.5, 2, None), "0,50");
143 assert_eq!(NumberHelper::format_decimal(1000.0, 0, None), "1 000");
144 }
145
146 #[test]
147 fn test_ordinal() {
148 assert_eq!(NumberHelper::ordinal(1), "1er");
149 assert_eq!(NumberHelper::ordinal(2), "2e");
150 assert_eq!(NumberHelper::ordinal(100), "100e");
151 }
152
153 #[test]
154 fn test_percentage() {
155 assert_eq!(NumberHelper::percentage(0.756, 1), "75.6%");
156 assert_eq!(NumberHelper::percentage(1.0, 0), "100%");
157 assert_eq!(NumberHelper::percentage(0.5, 0), "50%");
158 }
159
160 #[test]
161 fn test_clamp() {
162 assert_eq!(NumberHelper::clamp(5, 0, 10), 5);
163 assert_eq!(NumberHelper::clamp(-5, 0, 10), 0);
164 assert_eq!(NumberHelper::clamp(15, 0, 10), 10);
165 assert_eq!(NumberHelper::clamp(0.5, 0.0, 1.0), 0.5);
166 }
167
168 #[test]
169 fn test_duration_format() {
170 assert_eq!(NumberHelper::duration_format(0), "0s");
171 assert_eq!(NumberHelper::duration_format(45), "45s");
172 assert_eq!(NumberHelper::duration_format(90), "1min 30s");
173 assert_eq!(NumberHelper::duration_format(3600), "1h");
174 assert_eq!(NumberHelper::duration_format(3661), "1h 1min 1s");
175 assert_eq!(NumberHelper::duration_format(7200), "2h");
176 }
177
178 #[test]
179 fn test_round() {
180 assert_eq!(NumberHelper::round(3.14159, 2), 3.14);
181 assert_eq!(NumberHelper::round(3.145, 2), 3.15);
182 assert_eq!(NumberHelper::round(3.0, 2), 3.0);
183 }
184}