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);
#[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)
);
}
}