Merge PR #2678: Round FormCalc Str and Format exact ties away from zero

This commit is contained in:
aiongg
2026-10-16 14:02:07 +00:00
2 changed files with 22 additions and 2 deletions
+9 -2
View File
@@ -712,6 +712,13 @@ fn num_text(n: f64) -> String {
if s.is_empty() || s == "-0" { "0".into() } else { s.to_string() }
}
/// `v` with `decimals` places, rounding exact halves away from zero (Rust's formatter rounds them to even).
fn fixed(v: f64, decimals: usize) -> String {
let k = 10f64.powi(decimals.min(20) as i32);
let scaled = (v * k).round() / k;
format!("{:.decimals$}", if scaled.is_finite() { scaled } else { v })
}
/// The number a string holds: its leading numeric part (`12abc` → 12), `None` for none.
fn parse_num(s: &str) -> Option<f64> {
let t = s.trim();
@@ -2166,7 +2173,7 @@ impl Interp<'_> {
let v = self.arg_num(args, 0)?;
let width = if args.len() > 1 { self.arg_num(args, 1)?.clamp(0.0, 1000.0) as usize } else { 10 };
let prec = if args.len() > 2 { self.arg_num(args, 2)?.clamp(0.0, 20.0) as usize } else { 0 };
let text = format!("{v:.prec$}");
let text = fixed(v, prec);
Val::Str(if text.len() > width { "*".repeat(width) } else { format!("{text:>width$}") })
}
"substr" => {
@@ -2255,7 +2262,7 @@ impl Interp<'_> {
let num = self.to_num(&v);
let decimals = pic.split_once('.').map_or(0, |(_, f)| f.chars().filter(|c| matches!(c, '9' | 'z' | 'Z')).count()).min(20);
let grouped = pic.contains(',');
let mut text = format!("{:.decimals$}", num.abs());
let mut text = fixed(num.abs(), decimals);
if grouped {
let (int, frac) = text.split_once('.').map_or((text.as_str(), ""), |(a, b)| (a, b));
let mut g = String::new();
+13
View File
@@ -636,6 +636,19 @@ mod formcalc {
assert_eq!(calc("; a comment\n// another\nSum(1,\n 2,\n 3)"), "6");
}
#[test]
fn str_and_format_round_exact_ties_away_from_zero() {
assert_eq!(calc("Str(2.5, 3, 0)"), " 3");
assert_eq!(calc("Str(0.5, 3, 0)"), " 1");
assert_eq!(calc("Str(-2.5, 4, 0)"), " -3");
assert_eq!(calc("Str(1.125, 6, 2)"), " 1.13");
assert_eq!(calc(r#"Format("9", 2.5)"#), "3");
assert_eq!(calc(r#"Format("9.99", 0.125)"#), "0.13");
assert_eq!(calc(r#"Format("9.9", 0.25)"#), "0.3");
assert_eq!(calc(r#"Format("9.9", -0.25)"#), "-0.3");
assert_eq!(calc(r#"Format("9.99", 2.344)"#), "2.34");
}
#[test]
fn built_in_functions_cover_the_common_set() {
assert_eq!(calc("Abs(-2.5)"), "2.5");