gioser

tawasuyu/brahman

Rust 582TOML 222Markdown 73JSON 198635 Sutichaykuna

⎇ 634a43006ac4257288af40320b5360e9f157ab09 crates/modules/charka/charka-runtime/src/edited.rs Rust · 103 siq'ikuna · 3.2 KB Ñawpa willay Pi imata llamkarqan ch'uya

feat(charka): PICTURE de edición — Z, coma de millares y punto decimal 634a43006 · sergio ·

1//! `format_edited` — el formateo de un valor numérico según una
2//! PICTURE de edición (`ZZ,ZZ9.99`).
3
4use charka_bcd::{Decimal, Rounding};
5
6/// Formatea `value` según una PICTURE de edición. Soporta `9` (dígito),
7/// `Z` (dígito con supresión de ceros a la izquierda), `,` (coma de
8/// millares, en blanco dentro de la zona suprimida), `.` (punto
9/// decimal) y `B` (espacio). El signo se descarta.
10pub fn format_edited(value: Decimal, pic: &str) -> String {
11    let up = pic.to_uppercase();
12    let (int_pic, frac_pic) = match up.split_once('.') {
13        Some((a, b)) => (a, b),
14        None => (up.as_str(), ""),
15    };
16    let count_digits = |s: &str| s.chars().filter(|c| *c == '9' || *c == 'Z').count();
17    let int_digits = count_digits(int_pic);
18    let frac_digits = count_digits(frac_pic);
19    let total = int_digits + frac_digits;
20
21    // Los dígitos del valor, con exactamente `frac_digits` decimales.
22    let mantissa = value
23        .rescale(frac_digits as u8, Rounding::Truncate)
24        .mantissa()
25        .unsigned_abs();
26    let mut digits = mantissa.to_string();
27    if digits.len() < total {
28        digits = format!("{}{}", "0".repeat(total - digits.len()), digits);
29    } else if digits.len() > total {
30        digits = digits[digits.len() - total..].to_string();
31    }
32    let int_part = &digits.as_bytes()[..int_digits];
33    let frac_part = &digits.as_bytes()[int_digits..];
34
35    let mut out = String::new();
36    let mut di = 0;
37    let mut seen = false;
38    for ch in int_pic.chars() {
39        match ch {
40            '9' => {
41                out.push(int_part[di] as char);
42                di += 1;
43                seen = true;
44            }
45            'Z' => {
46                let d = int_part[di];
47                di += 1;
48                if seen || d != b'0' {
49                    out.push(d as char);
50                    seen = true;
51                } else {
52                    out.push(' ');
53                }
54            }
55            ',' => out.push(if seen { ',' } else { ' ' }),
56            'B' => out.push(' '),
57            other => out.push(other),
58        }
59    }
60    if !frac_pic.is_empty() {
61        out.push('.');
62        let mut fi = 0;
63        for ch in frac_pic.chars() {
64            match ch {
65                '9' | 'Z' => {
66                    out.push(frac_part[fi] as char);
67                    fi += 1;
68                }
69                other => out.push(other),
70            }
71        }
72    }
73    out
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    fn dec(s: &str) -> Decimal {
81        Decimal::parse(s).unwrap()
82    }
83
84    #[test]
85    fn suppresses_leading_zeros_and_inserts_commas() {
86        assert_eq!(format_edited(dec("1234.5"), "Z,ZZ9.99"), "1,234.50");
87        assert_eq!(format_edited(dec("7"), "Z,ZZ9.99"), "    7.00");
88        assert_eq!(format_edited(dec("0"), "Z,ZZ9.99"), "    0.00");
89    }
90
91    #[test]
92    fn comma_in_suppressed_zone_is_blank() {
93        // El millar va en blanco si no hay dígito significativo antes.
94        assert_eq!(format_edited(dec("42"), "ZZ,ZZ9"), "    42");
95        assert_eq!(format_edited(dec("12345"), "ZZ,ZZ9"), "12,345");
96    }
97
98    #[test]
99    fn nine_positions_always_show() {
100        assert_eq!(format_edited(dec("0"), "999"), "000");
101        assert_eq!(format_edited(dec("0"), "ZZZ"), "   ");
102    }
103}