nmbrs_workload/
magnitude.rs1pub fn parse_magnitude(s: &str) -> Option<f64> {
28 let t = s.trim();
29 if t.is_empty() {
30 return None;
31 }
32 if let Ok(v) = t.parse::<f64>() {
34 return Some(v);
35 }
36 let lower = t.to_ascii_lowercase();
37 const BINARY: &[(&str, u64)] = &[
40 ("ki", 1u64 << 10),
41 ("mi", 1u64 << 20),
42 ("gi", 1u64 << 30),
43 ("ti", 1u64 << 40),
44 ("pi", 1u64 << 50),
45 ("ei", 1u64 << 60),
46 ];
47 for (suf, factor) in BINARY {
48 if let Some(head) = lower.strip_suffix(suf) {
49 return head.trim().parse::<f64>().ok().map(|v| v * *factor as f64);
50 }
51 }
52 const DECIMAL: &[(&str, f64)] = &[
54 ("k", 1e3),
55 ("m", 1e6),
56 ("g", 1e9),
57 ("b", 1e9),
58 ("t", 1e12),
59 ("p", 1e15),
60 ("e", 1e18),
61 ];
62 for (suf, factor) in DECIMAL {
63 if let Some(head) = lower.strip_suffix(suf) {
64 return head.trim().parse::<f64>().ok().map(|v| v * factor);
65 }
66 }
67 None
68}
69
70fn emit_f64(v: f64) -> String {
73 let s = format!("{v}");
74 if s.contains('.') || s.contains('e') || s.contains('E') {
75 s
76 } else {
77 format!("{s}.0")
78 }
79}
80
81pub fn coerce_param_override(default_value: &str, override_value: &str) -> String {
89 let dt = default_value.trim();
90 let default_is_u64 = dt.parse::<u64>().is_ok();
91 let default_is_f64 = !default_is_u64 && dt.parse::<f64>().is_ok();
92 if !default_is_u64 && !default_is_f64 {
93 return override_value.to_string();
94 }
95 let ov = override_value.trim();
96 if ov.parse::<u64>().is_ok() || ov.parse::<f64>().is_ok() {
98 return override_value.to_string();
99 }
100 match parse_magnitude(ov) {
101 Some(v) if default_is_u64 && v.is_finite() && v >= 0.0 => {
102 format!("{}", v.round() as u64)
103 }
104 Some(v) if default_is_f64 && v.is_finite() => emit_f64(v),
105 _ => override_value.to_string(),
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn plain_numbers_parse_directly() {
117 assert_eq!(parse_magnitude("1000000"), Some(1_000_000.0));
118 assert_eq!(parse_magnitude("1e3"), Some(1000.0)); assert_eq!(parse_magnitude("3.14"), Some(3.14));
120 }
121
122 #[test]
123 fn decimal_suffixes() {
124 assert_eq!(parse_magnitude("10m"), Some(10_000_000.0));
125 assert_eq!(parse_magnitude("10M"), Some(10_000_000.0));
126 assert_eq!(parse_magnitude("5k"), Some(5_000.0));
127 assert_eq!(parse_magnitude("2b"), Some(2_000_000_000.0));
128 assert_eq!(parse_magnitude("1.5g"), Some(1_500_000_000.0));
129 assert_eq!(parse_magnitude("1E"), Some(1e18));
130 }
131
132 #[test]
133 fn binary_suffixes() {
134 assert_eq!(parse_magnitude("4Ki"), Some(4096.0));
135 assert_eq!(parse_magnitude("10mi"), Some(10.0 * (1u64 << 20) as f64));
136 assert_eq!(parse_magnitude("1Gi"), Some((1u64 << 30) as f64));
137 assert_ne!(parse_magnitude("1mi"), parse_magnitude("1m"));
139 }
140
141 #[test]
142 fn non_numbers_are_none() {
143 assert_eq!(parse_magnitude("abc"), None);
144 assert_eq!(parse_magnitude(""), None);
145 assert_eq!(parse_magnitude("m"), None); }
147
148 #[test]
149 fn coerce_anchors_to_numeric_default() {
150 assert_eq!(coerce_param_override("1000000", "10m"), "10000000");
152 assert_eq!(coerce_param_override("1000000", "10000000"), "10000000");
154 assert_eq!(coerce_param_override("1024", "4Ki"), "4096");
156 assert_eq!(coerce_param_override("3.5", "1m"), "1000000.0");
158 }
159
160 #[test]
161 fn coerce_leaves_string_params_alone() {
162 assert_eq!(coerce_param_override("*", "100m"), "100m");
165 assert_eq!(coerce_param_override("default", "100mi"), "100mi");
166 assert_eq!(coerce_param_override("1000000", "lots"), "lots");
168 }
169}