gioser

tawasuyu/brahman

Rust 476TOML 190Markdown 50JSON 176602 Definitions

⎇ 85156c15090f0ae5116737b7c07cddae23cbf7f3 crates/modules/charka/charka-runtime/src/num.rs Rust · 158 lines · 4.5 KB History Who touched what raw

feat(charka): charka-runtime — soporte de ejecución (campos Num y Text) 85156c150 · sergio ·

1//! `Num` — un campo numérico COBOL en tiempo de ejecución.
2
3use charka_bcd::{Decimal, Picture, Rounding};
4
5/// Un campo numérico: un valor [`Decimal`] más la [`Picture`] que lo
6/// conforma. Toda asignación pasa por la PICTURE — ese es el `MOVE` de
7/// COBOL: el valor se ajusta a la escala y al tamaño declarados.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct Num {
10    value: Decimal,
11    pic: Picture,
12}
13
14impl Num {
15    /// Campo nuevo en cero, con la PICTURE dada.
16    pub fn new(pic: Picture) -> Self {
17        Self {
18            value: Decimal::zero(),
19            pic,
20        }
21    }
22
23    /// Campo con un `VALUE` inicial (el texto del literal). Un literal
24    /// inválido deja el campo en cero.
25    pub fn with_value(pic: Picture, literal: &str) -> Self {
26        let mut n = Self::new(pic);
27        if let Ok(d) = Decimal::parse(literal) {
28            n.store(d);
29        }
30        n
31    }
32
33    /// El valor decimal actual.
34    pub fn value(&self) -> Decimal {
35        self.value
36    }
37
38    /// La PICTURE del campo.
39    pub fn picture(&self) -> Picture {
40        self.pic
41    }
42
43    /// Asigna un valor conformándolo a la PICTURE: ajusta la escala
44    /// truncando los dígitos fraccionarios sobrantes.
45    pub fn store(&mut self, v: Decimal) {
46        self.value = fit(v, &self.pic, Rounding::Truncate);
47    }
48
49    /// Como [`store`](Self::store) pero redondeando — el `ROUNDED`.
50    pub fn store_rounded(&mut self, v: Decimal) {
51        self.value = fit(v, &self.pic, Rounding::HalfUp);
52    }
53
54    /// Representación para `DISPLAY`: los dígitos del campo, rellenados
55    /// con ceros a la izquierda hasta el total de la PICTURE; con un
56    /// `-` adelante si el campo lleva signo y el valor es negativo.
57    pub fn display(&self) -> String {
58        let total = self.pic.total_digits() as usize;
59        let abs = self.value.mantissa().unsigned_abs().to_string();
60        let digits = if abs.len() >= total {
61            abs[abs.len() - total..].to_string()
62        } else {
63            format!("{}{}", "0".repeat(total - abs.len()), abs)
64        };
65        if self.pic.signed && self.value.is_negative() {
66            format!("-{digits}")
67        } else {
68            digits
69        }
70    }
71}
72
73/// Conforma un valor a una PICTURE. Si la parte entera no cabe (el
74/// `ON SIZE ERROR` de COBOL) y ninguna cláusula lo captura, COBOL deja
75/// los dígitos de bajo orden: reescalamos y enmascaramos.
76fn fit(v: Decimal, pic: &Picture, rounding: Rounding) -> Decimal {
77    if let Ok(d) = v.coerce(pic, rounding) {
78        return d;
79    }
80    let r = v.rescale(pic.fraction_digits, rounding);
81    let modulus = 10i128.pow(pic.total_digits() as u32);
82    let mut m = r.mantissa() % modulus;
83    if !pic.signed && m < 0 {
84        m = -m;
85    }
86    Decimal::new(m, pic.fraction_digits)
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    fn pic(s: &str) -> Picture {
94        Picture::parse(s).expect("PICTURE válida")
95    }
96
97    fn dec(s: &str) -> Decimal {
98        Decimal::parse(s).expect("decimal válido")
99    }
100
101    #[test]
102    fn new_field_is_zero() {
103        let n = Num::new(pic("9(5)"));
104        assert!(n.value().is_zero());
105        assert_eq!(n.display(), "00000");
106    }
107
108    #[test]
109    fn with_value_initializes() {
110        let n = Num::with_value(pic("9(3)"), "42");
111        assert_eq!(n.display(), "042");
112    }
113
114    #[test]
115    fn store_truncates_fraction() {
116        let mut n = Num::new(pic("9(3)V99"));
117        n.store(dec("12.3456"));
118        assert_eq!(n.value(), dec("12.34"));
119    }
120
121    #[test]
122    fn store_rounded_rounds_fraction() {
123        let mut n = Num::new(pic("9(3)V99"));
124        n.store_rounded(dec("12.3456"));
125        assert_eq!(n.value(), dec("12.35"));
126    }
127
128    #[test]
129    fn store_overflow_keeps_low_order_digits() {
130        // 1234 no cabe en 9(3): COBOL conserva los 3 dígitos bajos.
131        let mut n = Num::new(pic("9(3)"));
132        n.store(dec("1234"));
133        assert_eq!(n.display(), "234");
134    }
135
136    #[test]
137    fn unsigned_field_stores_magnitude() {
138        let mut n = Num::new(pic("9(3)"));
139        n.store(dec("-7"));
140        assert_eq!(n.display(), "007");
141        assert!(!n.value().is_negative());
142    }
143
144    #[test]
145    fn signed_field_keeps_sign_in_display() {
146        let mut n = Num::new(pic("S9(3)"));
147        n.store(dec("-7"));
148        assert_eq!(n.display(), "-007");
149    }
150
151    #[test]
152    fn display_includes_implied_fraction_digits() {
153        // PIC 9(2)V99, valor 7.5 → dígitos 0750.
154        let mut n = Num::new(pic("9(2)V99"));
155        n.store(dec("7.5"));
156        assert_eq!(n.display(), "0750");
157    }
158}