Skip to main content

ironwork_numeric/
binary.rs

1use crate::options::{Options, Trunc, TruncCheck};
2use zarch::wide::U256;
3
4/// A USAGE BINARY, COMP or COMP-4 item, or COMP-5 when `native`.
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub struct Binary {
7    pub digits: u8,
8    pub signed: bool,
9    pub native: bool,
10}
11
12impl Binary {
13    pub const fn bytes(self) -> usize {
14        match self.digits {
15            0..=4 => 2,
16            5..=9 => 4,
17            _ => 8,
18        }
19    }
20
21    pub fn load(self, bytes: &[u8]) -> i128 {
22        assert_eq!(bytes.len(), self.bytes());
23        let raw = bytes.iter().fold(0u128, |acc, &b| acc << 8 | b as u128);
24        let bits = 8 * bytes.len() as u32;
25        if self.signed && raw >> (bits - 1) == 1 { raw as i128 - (1i128 << bits) } else { raw as i128 }
26    }
27
28    fn decimal_truncation(self, value: i128) -> i128 {
29        let cap = if self.digits <= 38 { U256::pow10(u32::from(self.digits)).lo } else { 10u128.pow(u32::from(self.digits)) };
30        let magnitude = value.unsigned_abs();
31        let kept = (if magnitude < cap { magnitude } else { magnitude % cap }) as i128;
32        if value < 0 { -kept } else { kept }
33    }
34
35    fn binary_truncation(self, value: i128) -> i128 {
36        let bits = 8 * self.bytes() as u32;
37        let raw = (value as u128) & ((1u128 << bits) - 1);
38        if self.signed && raw >> (bits - 1) == 1 { raw as i128 - (1i128 << bits) } else { raw as i128 }
39    }
40}
41
42/// A TRUNC(OPT) store whose value exceeds the PICTURE, where decimal and binary truncation give
43/// different results and the program depends on which one the generated code happens to use.
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub struct TruncOptDivergence {
46    pub value: i128,
47    pub decimal: i128,
48    pub binary: i128,
49}
50
51#[derive(Clone, Debug, PartialEq, Eq)]
52pub struct Stored {
53    pub bytes: Vec<u8>,
54    pub value: i128,
55    pub divergence: Option<TruncOptDivergence>,
56}
57
58/// Stores `value`, already scaled to the item's decimal places. An unsigned item takes the absolute
59/// value. See [`crate::assumptions::TRUNC_OPT_IS_BINARY`] for what TRUNC(OPT) keeps.
60pub fn store(item: Binary, value: i128, options: &Options) -> Stored {
61    let (kept, divergence) = kept(item, value, options);
62    Stored { bytes: kept.to_be_bytes()[16 - item.bytes()..].to_vec(), value: kept, divergence }
63}
64
65/// The value `store` keeps, whose low-order `item.bytes()` bytes it stores, and its divergence.
66pub fn kept(item: Binary, value: i128, options: &Options) -> (i128, Option<TruncOptDivergence>) {
67    let value = if item.signed { value } else { value.unsigned_abs() as i128 };
68    let decimal = item.decimal_truncation(value);
69    let binary = item.binary_truncation(value);
70    match (item.native, options.trunc) {
71        (true, _) | (false, Trunc::Bin) => (binary, None),
72        (false, Trunc::Std) => (decimal, None),
73        (false, Trunc::Opt) => {
74            let report = decimal != binary && options.trunc_check == TruncCheck::Report;
75            (binary, report.then_some(TruncOptDivergence { value, decimal, binary }))
76        }
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use crate::options::Options;
84
85    const HALFWORD: Binary = Binary { digits: 4, signed: false, native: false };
86    const SIGNED_HALFWORD: Binary = Binary { digits: 4, signed: true, native: false };
87
88    fn with(trunc: Trunc) -> Options {
89        Options { trunc, ..Options::default() }
90    }
91
92    #[test]
93    fn sizes_follow_the_picture() {
94        let size = |digits| Binary { digits, signed: true, native: false }.bytes();
95        assert_eq!([size(1), size(4), size(5), size(9), size(10), size(18)], [2, 2, 4, 4, 8, 8]);
96    }
97
98    #[test]
99    fn std_truncates_to_the_picture() {
100        let s = store(HALFWORD, 12345, &with(Trunc::Std));
101        assert_eq!((s.value, s.bytes.as_slice()), (2345, [0x09, 0x29].as_slice()));
102        assert_eq!(store(SIGNED_HALFWORD, -12345, &with(Trunc::Std)).value, -2345);
103    }
104
105    #[test]
106    fn bin_truncates_to_the_halfword() {
107        assert_eq!(store(HALFWORD, 12345, &with(Trunc::Bin)).value, 12345);
108        assert_eq!(store(HALFWORD, 70000, &with(Trunc::Bin)).value, 70000 - 65536);
109        let s = store(SIGNED_HALFWORD, 40000, &with(Trunc::Bin));
110        assert_eq!((s.value, s.bytes.as_slice()), (40000 - 65536, [0x9C, 0x40].as_slice()));
111    }
112
113    #[test]
114    fn comp5_ignores_trunc() {
115        let native = Binary { native: true, ..HALFWORD };
116        assert_eq!(store(native, 12345, &with(Trunc::Std)).value, 12345);
117    }
118
119    #[test]
120    fn unsigned_items_take_the_absolute_value() {
121        assert_eq!(store(HALFWORD, -42, &with(Trunc::Std)).value, 42);
122    }
123
124    #[test]
125    fn opt_reports_a_store_where_decimal_and_binary_truncation_disagree() {
126        let s = store(HALFWORD, 12345, &with(Trunc::Opt));
127        assert_eq!(s.divergence, Some(TruncOptDivergence { value: 12345, decimal: 2345, binary: 12345 }));
128        assert_eq!(store(HALFWORD, 9999, &with(Trunc::Opt)).divergence, None);
129    }
130
131    #[test]
132    fn silent_stores_the_same_value_without_a_report() {
133        let mut options = with(Trunc::Opt);
134        options.apply_flag("-silent").unwrap();
135        let s = store(HALFWORD, 12345, &options);
136        assert_eq!((s.value, s.divergence), (12345, None));
137    }
138
139    #[test]
140    fn load_reads_twos_complement() {
141        assert_eq!(SIGNED_HALFWORD.load(&[0xFF, 0xFE]), -2);
142        assert_eq!(HALFWORD.load(&[0xFF, 0xFE]), 65534);
143    }
144}