⎇ 634a43006ac4257288af40320b5360e9f157ab09
crates/modules/charka/charka-runtime/src/edited.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
feat(charka): PICTURE de edición — Z, coma de millares y punto decimal
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}