1use numeric::Numproc;
5use crate::vocab::{SignClause, SignPosition};
6use zarch::check::ProgramCheck;
7use zarch::decimal::{self, Decimal};
8
9const PACKED_MAX: usize = 16;
11const ZONED_MAX: usize = 2 * PACKED_MAX - 1;
12const U64_DIGITS: usize = 18;
14
15pub fn packed(bytes: &[u8], signed: bool, numproc: Numproc) -> Result<Decimal, ProgramCheck> {
17 if signed || numproc != Numproc::Nopfd {
18 return decimal::decode(bytes);
19 }
20 let mut held = [0u8; PACKED_MAX];
21 let mut grown = Vec::new();
22 let p = match held.get_mut(..bytes.len()) {
23 Some(p) => p,
24 None => {
25 grown.extend_from_slice(bytes);
26 &mut grown[..]
27 }
28 };
29 p.copy_from_slice(bytes);
30 *p.last_mut().unwrap() |= 0x0F;
31 decimal::decode(p)
32}
33
34pub fn zoned(bytes: &[u8], signed: bool, sign: Option<SignClause>, numproc: Numproc) -> Result<Decimal, ProgramCheck> {
36 if matches!(sign, None | Some(SignClause { separate: false, position: SignPosition::Trailing })) && (1..=ZONED_MAX).contains(&bytes.len()) {
37 return packed_zoned(bytes, signed, numproc);
38 }
39 let mut held = [0u8; ZONED_MAX + 1];
40 let mut grown = Vec::new();
41 let mut zoned = match held.get_mut(..bytes.len()) {
42 Some(z) => z,
43 None => {
44 grown.extend_from_slice(bytes);
45 &mut grown[..]
46 }
47 };
48 zoned.copy_from_slice(bytes);
49 let mut separate_negative = None;
50 match sign {
51 Some(SignClause { separate: true, position }) => {
52 let whole = std::mem::take(&mut zoned);
53 let (s, rest) = if position == SignPosition::Leading { whole.split_first_mut().unwrap() } else { whole.split_last_mut().unwrap() };
54 separate_negative = Some(match *s {
55 0x60 => true,
56 0x4E => false,
57 _ => return Err(ProgramCheck::Data),
58 });
59 zoned = rest;
60 *zoned.last_mut().unwrap() |= 0xF0;
61 }
62 Some(SignClause { separate: false, position: SignPosition::Leading }) => {
63 let zone = zoned[0] & 0xF0;
64 zoned[0] |= 0xF0;
65 let last = zoned.len() - 1;
66 zoned[last] = zone | (zoned[last] & 0x0F);
67 }
68 _ => {}
69 }
70 let value = if (1..=ZONED_MAX).contains(&zoned.len()) { packed_zoned(zoned, signed, numproc)? } else { packed(&numeric::zoned::pack(zoned)?, signed, numproc)? };
71 Ok(match separate_negative {
72 Some(negative) => Decimal { negative, ..value },
73 None => value,
74 })
75}
76
77fn packed_zoned(zoned: &[u8], signed: bool, numproc: Numproc) -> Result<Decimal, ProgramCheck> {
80 let mut digit_bad = false;
81 let mut digit = |b: &u8| {
82 digit_bad |= b & 0x0F > 9;
83 b & 0x0F
84 };
85 let magnitude = if zoned.len() <= U64_DIGITS {
86 u128::from(zoned.iter().fold(0u64, |m, b| m * 10 + u64::from(digit(b))))
87 } else {
88 zoned.iter().fold(0u128, |m, b| m * 10 + u128::from(digit(b)))
89 };
90 let sign = if !signed && numproc == Numproc::Nopfd { 0x0F } else { zoned[zoned.len() - 1] >> 4 };
91 if digit_bad || !decimal::is_sign(sign) {
92 return Err(ProgramCheck::Data);
93 }
94 Ok(Decimal { negative: decimal::is_minus(sign), magnitude })
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 fn packed_by_copy(bytes: &[u8], signed: bool, numproc: Numproc) -> Result<Decimal, ProgramCheck> {
102 let mut p = bytes.to_vec();
103 if !signed && numproc == Numproc::Nopfd {
104 *p.last_mut().unwrap() |= 0x0F;
105 }
106 decimal::decode(&p)
107 }
108
109 fn zoned_by_pack(bytes: &[u8], signed: bool, sign: Option<SignClause>, numproc: Numproc) -> Result<Decimal, ProgramCheck> {
110 let mut zoned = bytes.to_vec();
111 let mut separate_negative = None;
112 match sign {
113 Some(SignClause { separate: true, position }) => {
114 let s = if position == SignPosition::Leading { zoned.remove(0) } else { zoned.pop().unwrap() };
115 separate_negative = Some(match s {
116 0x60 => true,
117 0x4E => false,
118 _ => return Err(ProgramCheck::Data),
119 });
120 *zoned.last_mut().unwrap() |= 0xF0;
121 }
122 Some(SignClause { separate: false, position: SignPosition::Leading }) => {
123 let zone = zoned[0] & 0xF0;
124 zoned[0] |= 0xF0;
125 let last = zoned.len() - 1;
126 zoned[last] = zone | (zoned[last] & 0x0F);
127 }
128 _ => {}
129 }
130 let p = numeric::zoned::pack(&zoned)?;
131 let value = packed_by_copy(&p, signed, numproc)?;
132 Ok(match separate_negative {
133 Some(negative) => Decimal { negative, ..value },
134 None => value,
135 })
136 }
137
138 fn fields(count: usize) -> Vec<Vec<u8>> {
140 let mut state = 0x2545_F491_4F6C_DD1Du64;
141 let mut next = move || {
142 state ^= state >> 12;
143 state ^= state << 25;
144 state ^= state >> 27;
145 state.wrapping_mul(0x9E37_79B9_7F4A_7C15) >> 32
146 };
147 const ZONES: [u8; 8] = [0xF0, 0xF0, 0xF0, 0xC0, 0xD0, 0xB0, 0x40, 0x00];
148 const SEPARATE: [u8; 3] = [0x60, 0x4E, 0xF1];
149 (0..count)
150 .map(|_| {
151 let len = 1 + (next() % 36) as usize;
152 let mut field: Vec<u8> = (0..len).map(|_| if next() % 20 == 0 { next() as u8 } else { 0xF0 | (next() % 10) as u8 }).collect();
153 let at = if next() % 2 == 0 { 0 } else { len - 1 };
154 field[at] = ZONES[(next() % 8) as usize] | (field[at] & 0x0F);
155 if next() % 3 == 0 {
156 field[at] = SEPARATE[(next() % 3) as usize];
157 }
158 field
159 })
160 .collect()
161 }
162
163 #[test]
164 fn zoned_and_packed_read_as_pack_then_decode_reads_them() {
165 let signs = [
166 None,
167 Some(SignClause { position: SignPosition::Trailing, separate: false }),
168 Some(SignClause { position: SignPosition::Leading, separate: false }),
169 Some(SignClause { position: SignPosition::Leading, separate: true }),
170 Some(SignClause { position: SignPosition::Trailing, separate: true }),
171 ];
172 let mut read = 0;
173 for field in fields(4000) {
174 for numproc in [Numproc::Nopfd, Numproc::Pfd] {
175 for signed in [false, true] {
176 assert_eq!(packed(&field, signed, numproc), packed_by_copy(&field, signed, numproc), "packed {field:02X?}");
177 for sign in signs {
178 if sign.is_some_and(|s| s.separate) && field.len() < 2 {
179 continue;
180 }
181 let value = zoned(&field, signed, sign, numproc);
182 assert_eq!(value, zoned_by_pack(&field, signed, sign, numproc), "zoned {field:02X?} {sign:?}");
183 read += usize::from(value.is_ok());
184 }
185 }
186 }
187 }
188 assert!(read > 10_000, "only {read} fields were valid");
189 }
190}