pub fn py_float_repr(x: f64) -> String {
if x.is_nan() {
return "nan".to_string();
}
if x.is_infinite() {
return if x > 0.0 { "inf" } else { "-inf" }.to_string();
}
let sci = format!("{:e}", x.abs());
let (mantissa, exp) = sci.split_once('e').unwrap_or((sci.as_str(), "0"));
let exp: i32 = exp.parse().unwrap_or(0);
let digits: String = mantissa.chars().filter(char::is_ascii_digit).collect();
let sign = if x.is_sign_negative() { "-" } else { "" };
if (-4..16).contains(&exp) {
let point = exp + 1;
let body = if point <= 0 {
format!("0.{}{digits}", "0".repeat(point.unsigned_abs() as usize))
} else if point as usize >= digits.len() {
format!("{digits}{}.0", "0".repeat(point as usize - digits.len()))
} else {
let (int, frac) = digits.split_at(point as usize);
format!("{int}.{frac}")
};
format!("{sign}{body}")
} else {
let (first, rest) = digits.split_at(1.min(digits.len()));
let frac = if rest.is_empty() {
String::new()
} else {
format!(".{rest}")
};
let esign = if exp < 0 { '-' } else { '+' };
format!("{sign}{first}{frac}e{esign}{:02}", exp.abs())
}
}
pub fn py_round(x: f64, ndigits: usize) -> f64 {
if !x.is_finite() {
return x;
}
let text = format!("{x:.ndigits$}");
text.parse().unwrap_or(x)
}
pub fn np_round(x: f64, decimals: i32) -> f64 {
let f = 10f64.powi(decimals);
(x * f).round_ties_even() / f
}
pub fn py_str_repr(s: &str) -> String {
let quote = if s.contains('\'') && !s.contains('"') {
'"'
} else {
'\''
};
let mut out = String::with_capacity(s.len() + 2);
out.push(quote);
for c in s.chars() {
match c {
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if c == quote => {
out.push('\\');
out.push(c);
}
c if (c as u32) < 0x20 || c as u32 == 0x7f => {
out.push_str(&format!("\\x{:02x}", c as u32));
}
c => out.push(c),
}
}
out.push(quote);
out
}
pub fn py_path_str(p: &str) -> String {
let root = if p.starts_with("//") && !p.starts_with("///") {
"//"
} else if p.starts_with('/') {
"/"
} else {
""
};
let parts: Vec<&str> = p
.split('/')
.filter(|part| !part.is_empty() && *part != ".")
.collect();
let joined = format!("{root}{}", parts.join("/"));
if joined.is_empty() {
".".to_string()
} else {
joined
}
}
pub fn py_path_join(root: &str, name: &str) -> String {
let base = py_path_str(root);
if base == "." {
py_path_str(name)
} else if base.ends_with('/') {
format!("{base}{name}")
} else {
format!("{base}/{name}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn float_repr_matches_python() {
let cases = [
(0.0, "0.0"),
(-0.0, "-0.0"),
(1.0, "1.0"),
(0.9, "0.9"),
(2.42, "2.42"),
(-76.4, "-76.4"),
(-240.0, "-240.0"),
(1e-5, "1e-05"),
(0.0001, "0.0001"),
(1e16, "1e+16"),
(123456789012345.6, "123456789012345.6"),
(1.5e-7, "1.5e-07"),
];
for (x, want) in cases {
assert_eq!(py_float_repr(x), want, "{x}");
}
}
#[test]
fn rounding_follows_python_and_numpy() {
assert_eq!(py_round(0.125, 2), 0.12);
assert_eq!(py_round(0.375, 2), 0.38);
assert_eq!(py_round(2.675, 2), 2.67);
assert_eq!(np_round(-0.04, 1), -0.0);
assert!(np_round(-0.04, 1).is_sign_negative());
assert_eq!(np_round(0.25, 1), 0.2);
assert_eq!(np_round(-76.44, 1), -76.4);
}
#[test]
fn str_and_path_forms_match_python() {
assert_eq!(py_str_repr("/a/b.wav"), "'/a/b.wav'");
assert_eq!(py_str_repr("it's"), "\"it's\"");
assert_eq!(py_path_join("/tmp/x/", "sounds.json"), "/tmp/x/sounds.json");
assert_eq!(py_path_join("./x//y/.", "a.wav"), "x/y/a.wav");
assert_eq!(py_path_join(".", "a.wav"), "a.wav");
assert_eq!(py_path_join("/", "a.wav"), "/a.wav");
assert_eq!(py_path_str(""), ".");
}
}