use zarch::check::ProgramCheck;
use zarch::decimal;
const PACK_MAX: usize = 16;
const PART: usize = 15;
pub fn pack(zoned: &[u8]) -> Result<Vec<u8>, ProgramCheck> {
let mut packed = vec![0u8; zoned.len() / 2 + 1];
pack_into(&mut packed, zoned)?;
Ok(packed)
}
fn pack_into(packed: &mut [u8], zoned: &[u8]) -> Result<(), ProgramCheck> {
if zoned.len() <= PACK_MAX {
return decimal::pack(packed, zoned);
}
let (high, low) = (zoned.len() - PART, packed.len() - PART.div_ceil(2));
pack_into(&mut packed[..=low], &zoned[..=high])?;
decimal::pack(&mut packed[low..], &zoned[high..])
}
#[cfg(test)]
mod tests {
use super::*;
fn one_pack(zoned: &[u8]) -> Vec<u8> {
let mut nibbles: Vec<u8> = zoned.iter().map(|b| b & 0x0F).collect();
nibbles.push(zoned.last().unwrap() >> 4);
if nibbles.len() % 2 == 1 {
nibbles.insert(0, 0);
}
nibbles.chunks(2).map(|p| p[0] << 4 | p[1]).collect()
}
fn zoned(digits: &str, sign_zone: u8) -> Vec<u8> {
let mut z: Vec<u8> = digits.bytes().map(|d| 0xF0 | (d - b'0')).collect();
let last = z.len() - 1;
z[last] = sign_zone << 4 | (z[last] & 0x0F);
z
}
#[test]
fn eighteen_digits_pack_into_ten_bytes() {
let z = zoned("999999999999999999", 0xF);
assert_eq!(pack(&z).unwrap(), [0x09, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9F]);
let n = zoned("123456789012345678", 0xD);
let p = pack(&n).unwrap();
assert_eq!(p, [0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x8D]);
assert_eq!(decimal::decode(&p).unwrap(), decimal::Decimal { negative: true, magnitude: 123_456_789_012_345_678 });
}
#[test]
fn every_length_packs_as_one_pack_would() {
let digits = "9876543210123456789012345678901";
for len in 1..=31 {
for sign in [0xC, 0xD, 0xF, 0x4] {
let mut z = zoned(&digits[..len], sign);
if len > 2 {
z[1] = 0x41;
}
assert_eq!(pack(&z).unwrap(), one_pack(&z), "{len} digits, sign {sign:X}");
}
}
}
#[test]
fn an_invalid_digit_survives_to_the_packed_form() {
let mut z = zoned("12345678901234567", 0xC);
z[3] = 0xFA;
let p = pack(&z).unwrap();
assert_eq!(decimal::decode(&p), Err(ProgramCheck::Data));
}
#[test]
fn nothing_is_there_to_pack_in_an_empty_operand() {
assert_eq!(pack(&[]), Err(ProgramCheck::Specification));
}
}