bcd 0.1.0

Binary coded decimal library
use std::cmp::{Eq, PartialEq};
use std::convert::TryInto;
use std::mem::size_of;
use std::num::TryFromIntError;
use std::ops::{Add, BitAnd, BitOr, BitXor, Not, Shr, Sub};

pub trait BcdMasks: Sized {
    fn sixes() -> Self;
    fn ones() -> Self;
    fn top_bcd() -> Self;
}

impl BcdMasks for u32 {
    #[inline]
    fn sixes() -> u32 {
        0x06666666
    }

    #[inline]
    fn ones() -> u32 {
        0x11111110
    }

    #[inline]
    fn top_bcd() -> u32 {
        0xF0000000
    }
}

impl BcdMasks for u64 {
    #[inline]
    fn sixes() -> u64 {
        0x0666666666666666
    }

    #[inline]
    fn ones() -> u64 {
        0x1111111111111110
    }

    #[inline]
    fn top_bcd() -> u64 {
        0xF000000000000000
    }
}

impl BcdMasks for u128 {
    #[inline]
    fn sixes() -> u128 {
        0x06666666666666666666666666666666
    }

    #[inline]
    fn ones() -> u128 {
        0x11111111111111111111111111111110
    }

    #[inline]
    fn top_bcd() -> u128 {
        0xF0000000000000000000000000000000
    }
}

pub trait BcdType:
    BcdMasks
    + Add<Output = Self>
    + BitAnd<Output = Self>
    + BitOr<Output = Self>
    + BitXor<Output = Self>
    + Not<Output = Self>
    + Sub<Output = Self>
    + Shr<Output = Self>
    + From<u8>
    + TryInto<u8, Error = TryFromIntError>
    + PartialEq
    + Eq
    + Copy
{
}

impl BcdType for u32 {}
impl BcdType for u64 {}
impl BcdType for u128 {}

#[derive(Eq, PartialEq, Debug, Clone, Copy)]
pub struct Bcd<T: BcdType>(pub T);

impl<T: BcdType> Bcd<T> {
    #[inline]
    pub const fn digits() -> usize {
        size_of::<T>() * 2
    }

    #[inline]
    pub fn digit(self, n: u8) -> u8 {
        let shift = (4 * n).into();
        ((self.0 >> shift) & 0xF.into()).try_into().unwrap()
    }

    #[inline]
    pub fn add_with_overflow(self, other: Self, overflow: bool) -> (Self, bool) {
        let (r1, o1) = self + other;
        let (r2, o2) = r1 + Bcd((if overflow { 1 } else { 0 }).into());
        (r2, o1 | o2)
    }
}

impl<T: BcdType> Add for Bcd<T> {
    type Output = (Self, bool);

    /// http://homepage.cs.uiowa.edu/~jones/bcd/bcd.html#packed
    #[inline]
    fn add(self, other: Self) -> Self::Output {
        if (self.0 & T::top_bcd() != 0.into()) || (other.0 & T::top_bcd() != 0.into()) {
            panic!("Addition will overflow");
        }
        let t1 = self.0 + T::sixes();
        let t2 = t1 + other.0;
        let t3 = t1 ^ other.0;
        let t4 = t2 ^ t3;
        let t5 = (!t4) & T::ones();
        let t6 = (t5 >> 2.into()) | (t5 >> 3.into());

        let t7 = t2 - t6;
        (Bcd(t7 & (!T::top_bcd())), t7 & T::top_bcd() != 0.into())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn correct_digits_u32() {
        let digits: Vec<_> = (0..Bcd::<u32>::digits())
            .map(|i| Bcd(0x01234567u32).digit(i as u8))
            .collect();

        assert_eq!(digits, vec![7, 6, 5, 4, 3, 2, 1, 0]);
    }

    #[test]
    fn correct_digits_u64() {
        let digits: Vec<_> = (0..Bcd::<u64>::digits())
            .map(|i| Bcd(0x0123456789012345u64).digit(i as u8))
            .collect();

        assert_eq!(digits, vec![5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]);
    }

    #[test]
    fn add_bcd_u32() {
        assert_eq!(
            Bcd(0x01234567u32) + Bcd(0x01234567),
            (Bcd(0x02469134), false)
        );
        assert_eq!(
            Bcd(0x05000000u32) + Bcd(0x05000000),
            (Bcd(0x00000000), true)
        );
        assert_eq!(
            Bcd(0x09000000u32) + Bcd(0x09000000),
            (Bcd(0x08000000), true)
        );
    }
}