pub fn parse_magnitude(s: &str) -> Option<f64> {
let t = s.trim();
if t.is_empty() {
return None;
}
if let Ok(v) = t.parse::<f64>() {
return Some(v);
}
let lower = t.to_ascii_lowercase();
const BINARY: &[(&str, u64)] = &[
("ki", 1u64 << 10),
("mi", 1u64 << 20),
("gi", 1u64 << 30),
("ti", 1u64 << 40),
("pi", 1u64 << 50),
("ei", 1u64 << 60),
];
for (suf, factor) in BINARY {
if let Some(head) = lower.strip_suffix(suf) {
return head.trim().parse::<f64>().ok().map(|v| v * *factor as f64);
}
}
const DECIMAL: &[(&str, f64)] = &[
("k", 1e3),
("m", 1e6),
("g", 1e9),
("b", 1e9),
("t", 1e12),
("p", 1e15),
("e", 1e18),
];
for (suf, factor) in DECIMAL {
if let Some(head) = lower.strip_suffix(suf) {
return head.trim().parse::<f64>().ok().map(|v| v * factor);
}
}
None
}
fn emit_f64(v: f64) -> String {
let s = format!("{v}");
if s.contains('.') || s.contains('e') || s.contains('E') {
s
} else {
format!("{s}.0")
}
}
pub fn coerce_param_override(default_value: &str, override_value: &str) -> String {
let dt = default_value.trim();
let default_is_u64 = dt.parse::<u64>().is_ok();
let default_is_f64 = !default_is_u64 && dt.parse::<f64>().is_ok();
if !default_is_u64 && !default_is_f64 {
return override_value.to_string();
}
let ov = override_value.trim();
if ov.parse::<u64>().is_ok() || ov.parse::<f64>().is_ok() {
return override_value.to_string();
}
match parse_magnitude(ov) {
Some(v) if default_is_u64 && v.is_finite() && v >= 0.0 => {
format!("{}", v.round() as u64)
}
Some(v) if default_is_f64 && v.is_finite() => emit_f64(v),
_ => override_value.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plain_numbers_parse_directly() {
assert_eq!(parse_magnitude("1000000"), Some(1_000_000.0));
assert_eq!(parse_magnitude("1e3"), Some(1000.0)); assert_eq!(parse_magnitude("3.14"), Some(3.14));
}
#[test]
fn decimal_suffixes() {
assert_eq!(parse_magnitude("10m"), Some(10_000_000.0));
assert_eq!(parse_magnitude("10M"), Some(10_000_000.0));
assert_eq!(parse_magnitude("5k"), Some(5_000.0));
assert_eq!(parse_magnitude("2b"), Some(2_000_000_000.0));
assert_eq!(parse_magnitude("1.5g"), Some(1_500_000_000.0));
assert_eq!(parse_magnitude("1E"), Some(1e18));
}
#[test]
fn binary_suffixes() {
assert_eq!(parse_magnitude("4Ki"), Some(4096.0));
assert_eq!(parse_magnitude("10mi"), Some(10.0 * (1u64 << 20) as f64));
assert_eq!(parse_magnitude("1Gi"), Some((1u64 << 30) as f64));
assert_ne!(parse_magnitude("1mi"), parse_magnitude("1m"));
}
#[test]
fn non_numbers_are_none() {
assert_eq!(parse_magnitude("abc"), None);
assert_eq!(parse_magnitude(""), None);
assert_eq!(parse_magnitude("m"), None); }
#[test]
fn coerce_anchors_to_numeric_default() {
assert_eq!(coerce_param_override("1000000", "10m"), "10000000");
assert_eq!(coerce_param_override("1000000", "10000000"), "10000000");
assert_eq!(coerce_param_override("1024", "4Ki"), "4096");
assert_eq!(coerce_param_override("3.5", "1m"), "1000000.0");
}
#[test]
fn coerce_leaves_string_params_alone() {
assert_eq!(coerce_param_override("*", "100m"), "100m");
assert_eq!(coerce_param_override("default", "100mi"), "100mi");
assert_eq!(coerce_param_override("1000000", "lots"), "lots");
}
}