refactor(format): improve number formatting and rounding
Improve number formatting and add comprehensive tests: - Implemented `round_half_away` to provide more intuitive rounding (e.g., 2.5 -> 3, -2.5 -> -3). - Updated `format_f64` to use this rounding logic. - Added extensive unit tests for `parse_number_format` and `format_f64` , covering various edge cases and rounding behaviors. Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q5_K_XL)
This commit is contained in:
180
src/format.rs
180
src/format.rs
@ -19,9 +19,16 @@ pub fn parse_number_format(fmt: &str) -> (bool, u8) {
|
|||||||
(comma, decimals)
|
(comma, decimals)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Round half away from zero (the "normal" rounding people expect).
|
||||||
|
fn round_half_away(n: f64, decimals: u8) -> f64 {
|
||||||
|
let factor = 10_f64.powi(decimals as i32);
|
||||||
|
(n * factor + n.signum() * 0.5).trunc() / factor
|
||||||
|
}
|
||||||
|
|
||||||
/// Format an f64 with optional comma grouping and decimal places.
|
/// Format an f64 with optional comma grouping and decimal places.
|
||||||
pub fn format_f64(n: f64, comma: bool, decimals: u8) -> String {
|
pub fn format_f64(n: f64, comma: bool, decimals: u8) -> String {
|
||||||
let formatted = format!("{:.prec$}", n, prec = decimals as usize);
|
let rounded = round_half_away(n, decimals);
|
||||||
|
let formatted = format!("{:.prec$}", rounded, prec = decimals as usize);
|
||||||
if !comma {
|
if !comma {
|
||||||
return formatted;
|
return formatted;
|
||||||
}
|
}
|
||||||
@ -48,3 +55,174 @@ pub fn format_f64(n: f64, comma: bool, decimals: u8) -> String {
|
|||||||
}
|
}
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// ── parse_number_format ────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_comma_and_zero_decimals() {
|
||||||
|
assert_eq!(parse_number_format(",.0"), (true, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_comma_and_two_decimals() {
|
||||||
|
assert_eq!(parse_number_format(",.2"), (true, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_no_comma_two_decimals() {
|
||||||
|
assert_eq!(parse_number_format(".2"), (false, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_comma_only() {
|
||||||
|
assert_eq!(parse_number_format(","), (true, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_empty_string() {
|
||||||
|
assert_eq!(parse_number_format(""), (false, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_dot_no_digits_after() {
|
||||||
|
// "." has nothing after the dot — parse::<u8> fails → default 0
|
||||||
|
assert_eq!(parse_number_format("."), (false, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_multiple_dots_uses_last() {
|
||||||
|
// rfind picks the last dot
|
||||||
|
assert_eq!(parse_number_format(",.1.3"), (true, 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── format_f64 basic ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_no_comma_zero_decimals() {
|
||||||
|
assert_eq!(format_f64(1234.5, false, 0), "1235");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_no_comma_two_decimals() {
|
||||||
|
assert_eq!(format_f64(1234.5, false, 2), "1234.50");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_comma_zero_decimals() {
|
||||||
|
assert_eq!(format_f64(1234.0, true, 0), "1,234");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_comma_two_decimals() {
|
||||||
|
assert_eq!(format_f64(1234.56, true, 2), "1,234.56");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── comma placement boundaries ─────────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_comma_exactly_three_digits() {
|
||||||
|
assert_eq!(format_f64(999.0, true, 0), "999");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_comma_four_digits() {
|
||||||
|
assert_eq!(format_f64(1000.0, true, 0), "1,000");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_comma_seven_digits() {
|
||||||
|
assert_eq!(format_f64(1234567.0, true, 0), "1,234,567");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_comma_millions_with_decimals() {
|
||||||
|
assert_eq!(format_f64(1234567.89, true, 2), "1,234,567.89");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── negative numbers ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_negative_with_comma() {
|
||||||
|
assert_eq!(format_f64(-1234.0, true, 0), "-1,234");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_negative_with_comma_and_decimals() {
|
||||||
|
assert_eq!(format_f64(-1234567.89, true, 2), "-1,234,567.89");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_negative_no_comma() {
|
||||||
|
assert_eq!(format_f64(-42.5, false, 1), "-42.5");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── edge values ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_zero() {
|
||||||
|
assert_eq!(format_f64(0.0, true, 2), "0.00");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_small_fraction() {
|
||||||
|
assert_eq!(format_f64(0.123, true, 2), "0.12");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_negative_small_fraction() {
|
||||||
|
assert_eq!(format_f64(-0.5, true, 1), "-0.5");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── rounding: half-away-from-zero ─────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn round_half_up_positive() {
|
||||||
|
// 2.5 → 3, not 2 (banker's would give 2)
|
||||||
|
assert_eq!(format_f64(2.5, false, 0), "3");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn round_half_down_negative() {
|
||||||
|
// -2.5 → -3, not -2 (away from zero)
|
||||||
|
assert_eq!(format_f64(-2.5, false, 0), "-3");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn round_half_at_one_decimal() {
|
||||||
|
// 1.25 → 1.3
|
||||||
|
assert_eq!(format_f64(1.25, false, 1), "1.3");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn round_below_half_truncates() {
|
||||||
|
assert_eq!(format_f64(1.24, false, 1), "1.2");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn round_above_half_rounds_up() {
|
||||||
|
assert_eq!(format_f64(1.26, false, 1), "1.3");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── format_value dispatch ──────────────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_value_number() {
|
||||||
|
let v = CellValue::Number(1234.0);
|
||||||
|
assert_eq!(format_value(Some(&v), true, 0), "1,234");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_value_text() {
|
||||||
|
let v = CellValue::Text("hello".into());
|
||||||
|
assert_eq!(format_value(Some(&v), true, 2), "hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_value_none() {
|
||||||
|
assert_eq!(format_value(None, true, 2), "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user