#![allow(clippy::cast_lossless)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_sign_loss)]
use super::U2;
#[cfg(not(feature = "std"))]
#[allow(unused_imports)]
use crate::extensions::*;
use crate::math::{ConstOne, ConstZero};
#[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))]
#[cfg_attr(
feature = "wincode",
derive(wincode::SchemaWrite, wincode::SchemaRead)
)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
#[allow(missing_docs)]
#[allow(clippy::struct_excessive_bools)]
#[cfg_attr(feature = "c_compatible", repr(C))]
pub struct U4 {
pub b0: bool,
pub b1: bool,
pub b2: bool,
pub b3: bool,
}
#[cfg(feature = "num_traits")]
impl num_traits::ToPrimitive for U4 {
fn to_f32(&self) -> Option<f32> {
Some(self.value().into())
}
fn to_f64(&self) -> Option<f64> {
Some(self.value().into())
}
fn to_i128(&self) -> Option<i128> {
Some(self.value().into())
}
fn to_i16(&self) -> Option<i16> {
Some(self.value().into())
}
fn to_i32(&self) -> Option<i32> {
Some(self.value().into())
}
fn to_i64(&self) -> Option<i64> {
Some(self.value().into())
}
fn to_i8(&self) -> Option<i8> {
self.value().to_i8()
}
fn to_isize(&self) -> Option<isize> {
Some(self.value().into())
}
fn to_u128(&self) -> Option<u128> {
Some(self.value().into())
}
fn to_u16(&self) -> Option<u16> {
Some(self.value().into())
}
fn to_u32(&self) -> Option<u32> {
Some(self.value().into())
}
fn to_u64(&self) -> Option<u64> {
Some(self.value().into())
}
fn to_u8(&self) -> Option<u8> {
Some(self.value())
}
}
#[cfg(feature = "num_traits")]
impl num_traits::NumCast for U4 {
fn from<T: num_traits::ToPrimitive>(n: T) -> Option<Self> {
Some(unsafe { Self::new(n.to_u8().unwrap_unchecked()) })
}
}
impl U4 {
#[must_use]
pub fn new(val: u8) -> Self {
assert!(val <= 0b1111, "Value out of range for U4 (must be 0..=15)");
Self {
b0: (val & 0b0001) != 0,
b1: (val & 0b0010) != 0,
b2: (val & 0b0100) != 0,
b3: (val & 0b1000) != 0,
}
}
#[must_use]
pub const fn from_u8_trunc(val: u8) -> Self {
Self {
b0: (val & 0b0001) != 0,
b1: (val & 0b0010) != 0,
b2: (val & 0b0100) != 0,
b3: (val & 0b1000) != 0,
}
}
#[must_use]
pub const fn value(self) -> u8 {
(self.b0 as u8)
| ((self.b1 as u8) << 1)
| ((self.b2 as u8) << 2)
| ((self.b3 as u8) << 3)
}
#[must_use]
pub const fn is_zero(self) -> bool {
!self.b0 && !self.b1 && !self.b2 && !self.b3
}
#[must_use]
pub const fn is_max(self) -> bool {
self.b0 && self.b1 && self.b2 && self.b3
}
#[must_use]
pub const fn wrapping_add(self, other: Self) -> Self {
Self::from_u8_trunc(self.value().wrapping_add(other.value()))
}
#[must_use]
pub const fn wrapping_sub(self, other: Self) -> Self {
Self::from_u8_trunc(self.value().wrapping_sub(other.value()))
}
#[must_use]
pub fn to_u2(self) -> U2 {
self.into()
}
#[must_use]
pub const fn from_u2_pair(high: U2, low: U2) -> Self {
Self {
b0: low.b0,
b1: low.b1,
b2: high.b0,
b3: high.b1,
}
}
#[must_use]
pub const fn split_to_u2_pair(self) -> (U2, U2) {
let high = U2 {
b0: self.b2,
b1: self.b3,
};
let low = U2 {
b0: self.b0,
b1: self.b1,
};
(high, low)
}
}
impl const core::ops::Not for U4 {
type Output = Self;
fn not(self) -> Self {
Self::from_u8_trunc(!self.value())
}
}
impl const core::ops::BitAnd for U4 {
type Output = Self;
fn bitand(self, rhs: Self) -> Self {
Self::from_u8_trunc(self.value() & rhs.value())
}
}
impl const core::ops::BitOr for U4 {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self::from_u8_trunc(self.value() | rhs.value())
}
}
impl const core::ops::BitXor for U4 {
type Output = Self;
fn bitxor(self, rhs: Self) -> Self {
Self::from_u8_trunc(self.value() ^ rhs.value())
}
}
impl const core::ops::Shl<usize> for U4 {
type Output = Self;
fn shl(self, rhs: usize) -> Self {
Self::from_u8_trunc(self.value() << rhs)
}
}
impl const core::ops::Shr<usize> for U4 {
type Output = Self;
fn shr(self, rhs: usize) -> Self {
Self::from_u8_trunc(self.value() >> rhs)
}
}
impl const core::ops::Add for U4 {
type Output = Self;
fn add(self, rhs: Self) -> Self {
self.wrapping_add(rhs)
}
}
impl core::ops::Sub for U4 {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
self.wrapping_sub(rhs)
}
}
impl const core::ops::Mul for U4 {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
Self::from_u8_trunc(self.value().wrapping_mul(rhs.value()))
}
}
impl const core::ops::Div for U4 {
type Output = Self;
fn div(self, rhs: Self) -> Self {
Self::from_u8_trunc(self.value().wrapping_div(rhs.value()))
}
}
#[macro_export]
macro_rules! u4 {
($val:expr) => {
U4::new($val)
};
}
macro_rules! impl_u4_conversion {
($($t:ty),* $(,)?) => {
$(
impl From<$t> for U4 {
fn from(val: $t) -> Self {
assert!((0..=15).contains(&val), "Value out of range for U4 (must be 0..=15)");
U4 {
b0: (val & 0b0001) != 0,
b1: (val & 0b0010) != 0,
b2: (val & 0b0100) != 0,
b3: (val & 0b1000) != 0,
}
}
}
#[cfg(feature = "num_traits")]
impl From<U4> for $t {
fn from(val: U4) -> Self {
let raw = (val.b0 as u8)
| ((val.b1 as u8) << 1)
| ((val.b2 as u8) << 2)
| ((val.b3 as u8) << 3);
unsafe{num_traits::NumCast::from(raw).unwrap_unchecked()}
}
}
)*
};
}
macro_rules! impl_u4_float_conversion {
($($f:ty),* $(,)?) => {
$(
impl From<$f> for U4 {
fn from(val: $f) -> Self {
assert!(val.is_finite(), "Cannot convert non-finite float to U4");
assert!(val.fract() == 0.0, "Cannot convert non-integer float to U4");
let as_int = val as i128;
assert!((0..=15).contains(&as_int), "Float value out of U4 range (must be 0.0 to 15.0)");
let val = as_int as u8;
U4 {
b0: (val & 0b0001) != 0,
b1: (val & 0b0010) != 0,
b2: (val & 0b0100) != 0,
b3: (val & 0b1000) != 0,
}
}
}
impl From<U4> for $f {
fn from(val: U4) -> Self {
let raw = (val.b0 as u8)
| ((val.b1 as u8) << 1)
| ((val.b2 as u8) << 2)
| ((val.b3 as u8) << 3);
raw as $f
}
}
)*
};
}
impl_u4_conversion!(u8, u16, u32, u64, u128, usize);
impl_u4_conversion!(i8, i16, i32, i64, i128, isize);
impl_u4_float_conversion!(f32, f64);
impl core::ops::Rem for U4 {
type Output = Self;
fn rem(self, rhs: Self) -> Self::Output {
Self::from_u8_trunc(self.value() % rhs.value())
}
}
#[cfg(feature = "num_traits")]
impl num_traits::One for U4 {
fn is_one(&self) -> bool
where
Self: PartialEq,
{
self.value() == 1
}
fn one() -> Self {
Self::from_u8_trunc(1)
}
}
#[cfg(feature = "num_traits")]
impl num_traits::Zero for U4 {
fn zero() -> Self {
Self::from_u8_trunc(0)
}
fn is_zero(&self) -> bool {
self.value() == 0
}
}
#[cfg(feature = "num_traits")]
impl num_traits::Num for U4 {
fn from_str_radix(
str: &str,
radix: u32,
) -> Result<Self, Self::FromStrRadixErr> {
let result = <u8 as num_traits::Num>::from_str_radix(str, radix);
match result {
Ok(r) => Result::Ok(Self::from_u8_trunc(r)),
Err(e) => Result::Err(e),
}
}
type FromStrRadixErr = ::core::num::ParseIntError;
}
impl ConstOne for U4 {
const ONE: Self = Self::from_u8_trunc(1);
}
impl ConstZero for U4 {
const ZERO: Self = Self::from_u8_trunc(0);
}