#![no_std]
#![warn(missing_docs)]
use core::{
error::Error,
fmt::{self, Debug, Display},
num::{IntErrorKind, ParseIntError},
ops::{
Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Div,
DivAssign, Mul, MulAssign, Not, Rem, RemAssign, Shl, Shr, Sub, SubAssign,
},
};
use num::{
Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, FromPrimitive, Num, NumCast, One,
PrimInt, Saturating, ToPrimitive, Unsigned, Zero,
cast::AsPrimitive,
traits::{
ConstOne, ConstZero, SaturatingAdd, SaturatingMul, SaturatingSub, WrappingAdd, WrappingSub,
},
};
use zerocopy::{ByteEq, ByteHash, Immutable, IntoBytes, KnownLayout, TryFromBytes, Unaligned};
pub mod unaligned;
pub use unaligned::U24;
static_assertions::assert_cfg!(target_endian = "little");
static_assertions::const_assert!(usize::BITS >= 32);
#[derive(
Debug,
TryFromBytes,
IntoBytes,
Immutable,
KnownLayout,
Unaligned,
Clone,
Copy,
PartialOrd,
Ord,
Default,
ByteHash,
ByteEq,
)]
#[repr(u8)]
#[doc(hidden)]
pub enum ZeroByte {
#[default]
Zero = 0,
}
#[derive(
Clone, Copy, TryFromBytes, IntoBytes, KnownLayout, Immutable, Default, ByteHash, ByteEq,
)]
#[repr(C, align(4))]
#[allow(non_camel_case_types)]
pub struct u24 {
data: [u8; 3],
msb: ZeroByte,
}
static_assertions::assert_eq_size!(u24, u32);
static_assertions::assert_eq_align!(u24, u32);
#[macro_export]
macro_rules! u24 {
(0) => {
<u24 as ::num::traits::ConstZero>::ZERO
};
(1) => {
<u24 as ::num::traits::ConstOne>::ONE
};
($v:expr) => {{
static_assertions::const_assert!($v <= u24::MAX.into_u32());
$crate::u24::truncating_from_u32($v)
}};
}
impl u24 {
pub const MAX: u24 = Self {
data: [0xFF, 0xFF, 0xFF],
msb: ZeroByte::Zero,
};
pub const MIN: u24 = Self {
data: [0x0, 0x0, 0x0],
msb: ZeroByte::Zero,
};
pub const BITS: u32 = 24;
pub const MAX_LEN: u32 = Self::MAX.into_u32() + 1;
const U32_DATA_MASK: u32 = 0x00_FFFFFF;
#[inline]
pub const fn from_le_bytes(bytes: [u8; 3]) -> Self {
Self {
data: bytes,
msb: ZeroByte::Zero,
}
}
#[inline]
pub const fn from_be_bytes(d: [u8; 3]) -> Self {
Self {
data: [d[2], d[1], d[0]],
msb: ZeroByte::Zero,
}
}
#[inline]
pub const fn to_le_bytes(self) -> [u8; 3] {
self.data
}
#[inline]
pub const fn to_be_bytes(self) -> [u8; 3] {
let d = self.data;
[d[2], d[1], d[0]]
}
#[inline]
pub const fn into_u32(self) -> u32 {
zerocopy::transmute!(self)
}
#[inline]
pub const fn truncating_from_u32(v: u32) -> Self {
unsafe { core::mem::transmute(v & Self::U32_DATA_MASK) }
}
#[inline]
pub const fn checked_from_u32(v: u32) -> Option<Self> {
if v > Self::MAX.into_u32() {
None
} else {
Some(Self::truncating_from_u32(v))
}
}
#[inline]
pub const fn saturating_from_u32(v: u32) -> Self {
match Self::checked_from_u32(v) {
Some(v) => v,
None => Self::MAX,
}
}
#[inline]
pub(crate) const fn must_from_u32(v: u32) -> Self {
#[cfg(debug_assertions)]
if v > Self::MAX.into_u32() {
panic!("value out of range for u24");
}
Self::truncating_from_u32(v)
}
#[inline]
pub const fn checked_add(self, other: Self) -> Option<Self> {
match self.into_u32().checked_add(other.into_u32()) {
Some(v) if v > Self::MAX.into_u32() => None,
Some(v) => Some(Self::truncating_from_u32(v)),
None => None,
}
}
#[inline]
pub const fn checked_sub(self, other: Self) -> Option<Self> {
match self.into_u32().checked_sub(other.into_u32()) {
Some(v) => Some(Self::truncating_from_u32(v)),
None => None,
}
}
#[inline]
pub const fn checked_mul(self, other: Self) -> Option<Self> {
match self.into_u32().checked_mul(other.into_u32()) {
Some(v) if v > Self::MAX.into_u32() => None,
Some(v) => Some(Self::truncating_from_u32(v)),
None => None,
}
}
#[inline]
pub const fn checked_div(self, other: Self) -> Option<Self> {
match self.into_u32().checked_div(other.into_u32()) {
Some(v) => Some(Self::truncating_from_u32(v)),
None => None,
}
}
#[inline]
pub const fn saturating_add(self, other: Self) -> Self {
match self.checked_add(other) {
Some(v) => v,
None => Self::MAX,
}
}
#[inline]
pub const fn saturating_sub(self, other: Self) -> Self {
match self.checked_sub(other) {
Some(v) => v,
None => Self::MIN,
}
}
#[inline]
pub const fn saturating_mul(self, other: Self) -> Self {
match self.checked_mul(other) {
Some(v) => v,
None => Self::MAX,
}
}
#[inline]
pub const fn wrapping_add(self, other: Self) -> Self {
Self::truncating_from_u32(self.into_u32().wrapping_add(other.into_u32()))
}
#[inline]
pub const fn wrapping_sub(self, other: Self) -> Self {
Self::truncating_from_u32(self.into_u32().wrapping_sub(other.into_u32()))
}
}
impl PrimInt for u24 {
#[inline]
fn count_ones(self) -> u32 {
self.into_u32().count_ones()
}
#[inline]
fn count_zeros(self) -> u32 {
(!self.into_u32() & Self::U32_DATA_MASK).count_ones()
}
#[inline]
fn leading_zeros(self) -> u32 {
if self == Self::ZERO {
24
} else {
(self.into_u32() << 8).leading_zeros()
}
}
#[inline]
fn trailing_zeros(self) -> u32 {
if self == Self::ZERO {
24
} else {
self.into_u32().trailing_zeros()
}
}
#[inline]
fn rotate_left(self, n: u32) -> Self {
let n = n % 24; let x = self.into_u32();
Self::truncating_from_u32((x << n) | (x >> (24 - n)))
}
#[inline]
fn rotate_right(self, n: u32) -> Self {
let n = n % 24; let x = self.into_u32();
Self::truncating_from_u32((x >> n) | (x << (24 - n)))
}
#[inline]
fn signed_shl(self, n: u32) -> Self {
debug_assert!(n <= u24::BITS, "attempt to shift left with overflow");
Self::truncating_from_u32(((self.into_u32() as i32) << n) as u32)
}
#[inline]
fn signed_shr(self, n: u32) -> Self {
debug_assert!(n <= u24::BITS, "attempt to shift right with overflow");
Self::truncating_from_u32(((self.into_u32() as i32) >> n) as u32)
}
#[inline]
fn unsigned_shl(self, n: u32) -> Self {
self << (n as usize)
}
#[inline]
fn unsigned_shr(self, n: u32) -> Self {
self >> (n as usize)
}
#[inline]
fn swap_bytes(self) -> Self {
let d = self.data;
Self {
data: [d[2], d[1], d[0]],
msb: ZeroByte::Zero,
}
}
#[inline]
fn from_be(x: Self) -> Self {
x.swap_bytes()
}
#[inline]
fn from_le(x: Self) -> Self {
x
}
#[inline]
fn to_be(self) -> Self {
self.swap_bytes()
}
#[inline]
fn to_le(self) -> Self {
self
}
#[inline]
fn pow(self, exp: u32) -> Self {
Self::must_from_u32(self.into_u32().pow(exp))
}
}
impl Zero for u24 {
fn zero() -> Self {
Self::MIN
}
fn is_zero(&self) -> bool {
*self == Self::MIN
}
}
impl ConstZero for u24 {
const ZERO: Self = u24::MIN;
}
impl One for u24 {
fn one() -> Self {
Self::ONE
}
}
impl ConstOne for u24 {
const ONE: Self = Self {
data: [0x1, 0x0, 0x0],
msb: ZeroByte::Zero,
};
}
impl Debug for u24 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.into_u32())
}
}
impl Display for u24 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.into_u32())
}
}
#[derive(Debug)]
pub struct ParseU24Err(IntErrorKind);
impl ParseU24Err {
pub fn kind(&self) -> &IntErrorKind {
&self.0
}
}
impl fmt::Display for ParseU24Err {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
IntErrorKind::Empty => write!(f, "cannot parse integer from empty string"),
IntErrorKind::InvalidDigit => write!(f, "invalid digit found in string"),
IntErrorKind::PosOverflow => write!(f, "number too large to fit in target type"),
IntErrorKind::NegOverflow => write!(f, "number too small to fit in target type"),
IntErrorKind::Zero => write!(f, "number would be zero for non-zero type"),
other => write!(f, "unknown error: {other:?}"),
}
}
}
impl Error for ParseU24Err {}
impl From<ParseIntError> for ParseU24Err {
fn from(err: ParseIntError) -> Self {
Self(*err.kind())
}
}
impl Num for u24 {
type FromStrRadixErr = ParseU24Err;
fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
let v = u32::from_str_radix(str, radix)?;
if v > Self::MAX.into_u32() {
Err(ParseU24Err(IntErrorKind::PosOverflow))
} else {
Ok(Self::truncating_from_u32(v))
}
}
}
impl PartialOrd for u24 {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for u24 {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.into_u32().cmp(&other.into_u32())
}
}
macro_rules! impl_cmp_eq_small_unsigned {
($($ty:ty),*) => {
$(
impl PartialEq<$ty> for u24 {
fn eq(&self, other: &$ty) -> bool {
self.into_u32() == *other as u32
}
}
impl PartialOrd<$ty> for u24 {
fn partial_cmp(&self, other: &$ty) -> Option<core::cmp::Ordering> {
Some(self.into_u32().cmp(&(*other as u32)))
}
}
)*
};
}
macro_rules! impl_cmp_eq_small_signed {
($($ty:ty),*) => {
$(
impl PartialEq<$ty> for u24 {
fn eq(&self, other: &$ty) -> bool {
*other >= 0 && self.into_u32() == *other as u32
}
}
impl PartialOrd<$ty> for u24 {
fn partial_cmp(&self, other: &$ty) -> Option<core::cmp::Ordering> {
if *other < 0 {
Some(core::cmp::Ordering::Greater)
} else {
Some(self.into_u32().cmp(&(*other as u32)))
}
}
}
)*
};
}
macro_rules! impl_cmp_eq_self_upcast {
($($ty:ty),*) => {
$(
impl PartialEq<$ty> for u24 {
fn eq(&self, other: &$ty) -> bool {
(self.into_u32() as $ty) == *other
}
}
impl PartialOrd<$ty> for u24 {
fn partial_cmp(&self, other: &$ty) -> Option<core::cmp::Ordering> {
Some((self.into_u32() as $ty).cmp(other))
}
}
)*
};
}
impl_cmp_eq_small_unsigned!(u8, u16);
impl_cmp_eq_small_signed!(i8, i16);
impl_cmp_eq_self_upcast!(usize, u32, u64, i32, i64);
macro_rules! impl_bin_op {
($(($op:ident, $meth:ident, $assign_op:ident, $assign_meth:ident, $op_fn:ident),)*) => {
$(
impl_bin_op!(@ u24, $op, $meth, $assign_op, $assign_meth, $op_fn);
impl_bin_op!(@ &u24, $op, $meth, $assign_op, $assign_meth, $op_fn);
)*
};
(@ $ty:ty, $op:ident, $meth:ident, $assign_op:ident, $assign_meth:ident, $op_fn:ident) => {
impl $op<$ty> for u24 {
type Output = Self;
#[inline(always)]
fn $meth(self, other: $ty) -> Self {
Self::must_from_u32(Self::into_u32(self).$op_fn(other.into_u32()))
}
}
impl $op<$ty> for &u24 {
type Output = u24;
#[inline(always)]
fn $meth(self, other: $ty) -> u24 {
<u24 as $op<$ty>>::$meth(*self, other)
}
}
impl $assign_op<$ty> for u24 {
#[inline(always)]
fn $assign_meth(&mut self, rhs: $ty) {
*self = $op::$meth(*self, rhs)
}
}
}
}
impl_bin_op!(
(Add, add, AddAssign, add_assign, wrapping_add),
(Sub, sub, SubAssign, sub_assign, wrapping_sub),
(Mul, mul, MulAssign, mul_assign, wrapping_mul),
(Div, div, DivAssign, div_assign, wrapping_div),
(Rem, rem, RemAssign, rem_assign, wrapping_rem),
(BitAnd, bitand, BitAndAssign, bitand_assign, bitand),
(BitOr, bitor, BitOrAssign, bitor_assign, bitor),
(BitXor, bitxor, BitXorAssign, bitxor_assign, bitxor),
);
impl Shl<usize> for u24 {
type Output = u24;
#[inline]
fn shl(self, rhs: usize) -> Self::Output {
debug_assert!(
rhs <= u24::BITS as usize,
"attempt to shift left with overflow"
);
Self::truncating_from_u32(self.into_u32() << rhs)
}
}
impl Shr<usize> for u24 {
type Output = u24;
#[inline]
fn shr(self, rhs: usize) -> Self::Output {
debug_assert!(
rhs <= u24::BITS as usize,
"attempt to shift right with overflow"
);
Self::truncating_from_u32(self.into_u32() >> rhs)
}
}
impl Not for u24 {
type Output = u24;
#[inline]
fn not(self) -> Self::Output {
Self::truncating_from_u32(!self.into_u32())
}
}
impl Unsigned for u24 {}
macro_rules! forward_impl {
($(($trait:ty, $method:ident, $return:ty),)*) => {
$(
impl $trait for u24 {
#[inline]
fn $method(&self, other: &Self) -> $return {
Self::$method(*self, *other)
}
}
)*
};
}
forward_impl!(
(CheckedAdd, checked_add, Option<u24>),
(CheckedSub, checked_sub, Option<u24>),
(CheckedMul, checked_mul, Option<u24>),
(CheckedDiv, checked_div, Option<u24>),
(SaturatingAdd, saturating_add, u24),
(SaturatingSub, saturating_sub, u24),
(SaturatingMul, saturating_mul, u24),
(WrappingAdd, wrapping_add, u24),
(WrappingSub, wrapping_sub, u24),
);
impl Saturating for u24 {
#[inline]
fn saturating_add(self, v: Self) -> Self {
Self::saturating_add(self, v)
}
#[inline]
fn saturating_sub(self, v: Self) -> Self {
Self::saturating_sub(self, v)
}
}
impl NumCast for u24 {
#[inline]
fn from<T: num::ToPrimitive>(n: T) -> Option<Self> {
n.to_u32().and_then(Self::checked_from_u32)
}
}
impl ToPrimitive for u24 {
#[inline]
fn to_i64(&self) -> Option<i64> {
Some(Self::into_u32(*self) as i64)
}
#[inline]
fn to_u64(&self) -> Option<u64> {
Some(Self::into_u32(*self) as u64)
}
#[inline]
fn to_u32(&self) -> Option<u32> {
Some(Self::into_u32(*self))
}
}
impl FromPrimitive for u24 {
#[inline]
fn from_i64(n: i64) -> Option<Self> {
<u32 as FromPrimitive>::from_i64(n).and_then(Self::checked_from_u32)
}
#[inline]
fn from_u64(n: u64) -> Option<Self> {
<u32 as FromPrimitive>::from_u64(n).and_then(Self::checked_from_u32)
}
}
impl Bounded for u24 {
#[inline]
fn min_value() -> Self {
Self::MIN
}
#[inline]
fn max_value() -> Self {
Self::MAX
}
}
macro_rules! impl_as {
($($ty:ty),*) => {
$(
impl AsPrimitive<$ty> for u24 {
#[inline]
fn as_(self) -> $ty {
self.into_u32() as $ty
}
}
impl AsPrimitive<u24> for $ty {
#[inline]
fn as_(self) -> u24 {
u24::truncating_from_u32(self.as_())
}
}
)*
};
}
impl_as!(usize, u64, u32, u16, u8, i64, i32, i16, i8);
impl AsPrimitive<u24> for u24 {
#[inline]
fn as_(self) -> u24 {
self
}
}
#[cfg(feature = "rangemap")]
impl rangemap::StepLite for u24 {
#[inline]
fn add_one(&self) -> Self {
self + Self::ONE
}
#[inline]
fn sub_one(&self) -> Self {
self - Self::ONE
}
}
#[cfg(feature = "range-set-blaze")]
impl range_set_blaze::Integer for u24 {
type SafeLen = u32;
#[inline]
fn checked_add_one(self) -> Option<Self> {
self.checked_add(u24::ONE)
}
#[inline]
fn add_one(self) -> Self {
self + u24::ONE
}
#[inline]
fn sub_one(self) -> Self {
self - u24::ONE
}
#[inline]
fn assign_sub_one(&mut self) {
*self -= u24::ONE
}
fn range_next(range: &mut core::ops::RangeInclusive<Self>) -> Option<Self> {
use core::cmp::Ordering;
let (start, end) = (*range.start(), *range.end());
match start.cmp(&end) {
Ordering::Less => {
*range = (start + u24::ONE)..=end;
Some(start)
}
Ordering::Equal => {
*range = u24::ONE..=u24::ZERO;
Some(start)
}
Ordering::Greater => None,
}
}
fn range_next_back(range: &mut core::ops::RangeInclusive<Self>) -> Option<Self> {
use core::cmp::Ordering;
let (end, start) = (*range.end(), *range.start());
match end.cmp(&start) {
Ordering::Greater => {
*range = start..=(end - u24::ONE);
Some(end)
}
Ordering::Equal => {
*range = u24::ONE..=u24::ZERO;
Some(end)
}
Ordering::Less => None,
}
}
#[inline]
fn min_value() -> Self {
u24::MIN
}
#[inline]
fn max_value() -> Self {
u24::MAX
}
fn safe_len(
range: &core::ops::RangeInclusive<Self>,
) -> <Self as range_set_blaze::Integer>::SafeLen {
let (start, end) = (*range.start(), *range.end());
if start <= end {
(end - start).into_u32() + 1
} else {
0
}
}
#[inline]
fn f64_to_safe_len_lossy(f: f64) -> Self::SafeLen {
f as Self::SafeLen
}
#[inline]
fn safe_len_to_f64_lossy(len: Self::SafeLen) -> f64 {
len as f64
}
#[inline]
fn inclusive_end_from_start(self, b: Self::SafeLen) -> Self {
debug_assert!(b > 0 && b <= Self::MAX_LEN, "b must be in range 1..=2**24");
self.wrapping_add(Self::truncating_from_u32(b - 1))
}
#[inline]
fn start_from_inclusive_end(self, b: Self::SafeLen) -> Self {
debug_assert!(b > 0 && b <= Self::MAX_LEN, "b must be in range 1..=2**24");
self.wrapping_sub(Self::truncating_from_u32(b - 1))
}
}
#[cfg(test)]
mod tests {
extern crate std;
use std::println;
use std::string::ToString;
use super::*;
use num::{Bounded, FromPrimitive, Num, NumCast, One, PrimInt, ToPrimitive, Zero};
#[test]
fn test_constants() {
let test_cases = [
("MIN", u24::MIN.into_u32(), 0),
("MAX", u24::MAX.into_u32(), 0x00_FFFFFF),
("ZERO", u24::ZERO.into_u32(), 0),
("ONE", u24::ONE.into_u32(), 1),
];
for (name, actual, expected) in test_cases {
assert_eq!(actual, expected, "u24::{} should equal {}", name, expected);
}
assert_eq!(u24::BITS, 24, "u24::BITS should equal 24");
}
#[test]
fn test_byte_conversions() {
let test_cases = [
([0x34, 0x12, 0xAB], 0x00_AB1234),
([0xFF, 0xFF, 0xFF], 0x00_FFFFFF),
([0x00, 0x00, 0x00], 0x00_000000),
([0x01, 0x00, 0x00], 0x00_000001),
];
for (input_bytes, expected_u32) in test_cases {
let val = u24::from_le_bytes(input_bytes);
assert_eq!(
val.to_le_bytes(),
input_bytes,
"Round-trip conversion failed for {:?}",
input_bytes
);
assert_eq!(
val.into_u32(),
expected_u32,
"u32 conversion failed for {:?}: expected {:#08X}, got {:#08X}",
input_bytes,
expected_u32,
val.into_u32()
);
}
assert_eq!(u24::from_le_bytes([0xFF, 0xFF, 0xFF]), u24::MAX);
}
#[test]
fn test_u32_conversions() {
let cases = [
(0x00_000000, Some(u24::MIN)),
(0x00_000001, Some(u24::ONE)),
(0x00_FFFFFF, Some(u24::MAX)),
(0x01_000000, None),
(0xFF_FFFFFF, None),
];
for (input, expected) in cases {
assert_eq!(
u24::checked_from_u32(input),
expected,
"checked_from_u32({:#08X}) should return {:?}",
input,
expected
);
}
let truncating_cases = [
(0x01_234567, 0x00_234567),
(0xFF_FFFFFF, 0x00_FFFFFF),
(0x00_123456, 0x00_123456),
(0xFF_000000, 0x00_000000),
];
for (input, expected) in truncating_cases {
assert_eq!(
u24::truncating_from_u32(input).into_u32(),
expected,
"truncating_from_u32({:#08X}) should return {:#08X}",
input,
expected
);
}
let saturating_cases = [
(0x00_000000, u24::MIN),
(0x01_FFFFFF, u24::MAX),
(0x01_000000, u24::MAX),
(0xFF_FFFFFF, u24::MAX),
];
for (input, expected) in saturating_cases {
assert_eq!(
u24::saturating_from_u32(input),
expected,
"saturating_from_u32({:#08X}) should return {:?}",
input,
expected
);
}
}
#[test]
fn test_math() {
macro_rules! test_op {
($( $op:tt($a:expr, $b:expr) $(= $expected:expr)? ),+ $(,)?) => {
$(
test_op!(@ $op($a, $b) $(= $expected)?);
)+
};
(@ $op:tt($a:expr, $b:expr)) => {
let u32a: u32 = $a;
let u32b: u32 = $b;
let expected = u32a.$op(u32b);
test_op!(@ $op($a, $b) = u24::must_from_u32(expected));
};
(@ $op:tt($a:expr, $b:expr) = $expected:expr) => {
let u24a = u24::must_from_u32($a);
let u24b = u24::must_from_u32($b);
println!(concat!("testing {}.", stringify!($op), "({}) = {:?}"), u24a, u24b, $expected);
let actual = u24a.$op(u24b);
assert_eq!(
actual,
$expected,
concat!("testing {}.", stringify!($op), "({}) = {:?}"),
u24a,
u24b,
actual
);
};
}
let u24_none = Option::<u24>::None;
test_op!(
add(0, 0),
add(1, 0),
add(1, 1),
add(128, 256),
add(0xFFFFFE, 1),
saturating_add(0xFFFFFF, 1) = u24::MAX,
saturating_add(1, 1),
checked_add(0xFFFFFE, 1) = Some(u24::MAX),
checked_add(0xFFFFFF, 1) = u24_none,
sub(1, 0),
sub(1, 1),
sub(256, 128),
sub(0xFFFFFF, 1),
saturating_sub(0, 1),
saturating_sub(1, 0),
checked_sub(0, 1) = u24_none,
checked_sub(1, 1) = Some(u24::ZERO),
mul(0, 0),
mul(1, 0),
mul(1, 1),
mul(128, 256),
mul(0xFFFFFF, 1),
mul(0x7FFFFF, 2),
saturating_mul(0xFFFFFF, 2) = u24::MAX,
saturating_mul(1, 2),
checked_mul(1, 2) = Some(u24!(2)),
checked_mul(0xFFFFFF, 2) = u24_none,
div(0, 1),
div(1, 1),
div(256, 128),
div(0xFFFFFF, 1),
div(0xFFFFFF, 0xFF),
checked_div(0, 0) = u24_none,
checked_div(0xFFFFFF, 1) = Some(u24::MAX),
rem(0, 1),
rem(1, 1),
rem(128, 256),
rem(0xFFFFFF, 1),
rem(0xFFFFFF, 0xFFFFFF),
bitand(0xFF_FFFF, 0xFF_FFFF),
bitand(0x00_0000, 0x00_0000),
bitxor(0xFF_FFFF, 0x00_0000),
bitand(0x12_3456, 0x23_4567),
bitor(0xFF_FFFF, 0xFF_FFFF),
bitor(0x00_0000, 0x00_0000),
bitor(0xFF_FFFF, 0x00_0000),
bitor(0x12_3456, 0x23_4567),
bitxor(0xFF_FFFF, 0xFF_FFFF),
bitxor(0x00_0000, 0x00_0000),
bitxor(0xFF_FFFF, 0x00_0000),
bitxor(0x12_3456, 0x23_4567),
);
}
#[test]
#[should_panic]
fn test_overflow_behavior() {
let _ = u24::MAX + u24::ONE;
}
#[test]
#[should_panic]
fn test_underflow_behavior() {
let _ = u24::ZERO - u24::ONE;
}
#[test]
fn test_not_op() {
let cases = [
(0x00_0000, 0xFF_FFFF),
(0xFF_FFFF, 0x00_0000),
(0xAA_AAAA, 0x55_5555),
(0x12_3456, 0xED_CBA9),
];
for (input_val, expected_not) in cases {
let input = u24::truncating_from_u32(input_val);
assert_eq!(
(!input).into_u32(),
expected_not,
"!{:#08X} should equal {:#08X}",
input_val,
expected_not
);
}
}
#[test]
fn test_shift_operations() {
let left_shift_cases = [
(0x12_3456, 0, 0x12_3456),
(0x12_3456, 4, 0x23_4560),
(0x12_3456, 8, 0x34_5600),
(0x12_3456, 16, 0x56_0000),
(0x12_3456, 24, 0x00_0000),
(0xFF_FFFF, 1, 0xFF_FFFE),
];
for (input_val, shift_amount, expected) in left_shift_cases {
let input = u24::truncating_from_u32(input_val);
assert_eq!(
(input << shift_amount).into_u32(),
expected,
"{:#08X} << {} should equal {:#08X}",
input_val,
shift_amount,
expected
);
}
let right_shift_cases = [
(0x12_3456, 0, 0x12_3456),
(0x12_3456, 4, 0x01_2345),
(0x12_3456, 8, 0x00_1234),
(0x12_3456, 16, 0x00_0012),
(0x12_3456, 24, 0x00_0000),
(0xFF_FFFF, 1, 0x7F_FFFF),
];
for (input_val, shift_amount, expected) in right_shift_cases {
let input = u24::truncating_from_u32(input_val);
assert_eq!(
(input >> shift_amount).into_u32(),
expected,
"{:#08X} >> {} should equal {:#08X}",
input_val,
shift_amount,
expected
);
}
}
#[test]
#[should_panic]
fn test_shl_overflow_behavior() {
let _ = u24::MAX << 25;
}
#[test]
#[should_panic]
fn test_shr_overflow_behavior() {
let _ = u24::MAX >> 25;
}
#[test]
fn test_rotation() {
let val = u24!(0x12_3456);
assert_eq!(val.rotate_left(4).into_u32(), 0x23_4561);
assert_eq!(val.rotate_left(8).into_u32(), 0x34_5612);
assert_eq!(val.rotate_left(24).into_u32(), val.into_u32());
assert_eq!(val.rotate_right(4).into_u32(), 0x61_2345);
assert_eq!(val.rotate_right(8).into_u32(), 0x56_1234);
assert_eq!(val.rotate_right(24).into_u32(), val.into_u32()); }
#[test]
fn test_bit_counting() {
let val = u24!(0xFF_0000);
assert_eq!(val.count_ones(), 8);
assert_eq!(val.count_zeros(), 16);
let val = u24!(0x00_00FF);
assert_eq!(val.count_ones(), 8);
assert_eq!(val.count_zeros(), 16);
assert_eq!(u24::ZERO.count_ones(), 0);
assert_eq!(u24::ZERO.count_zeros(), 24);
assert_eq!(u24::MAX.count_ones(), 24);
assert_eq!(u24::MAX.count_zeros(), 0);
}
#[test]
fn test_leading_trailing_zeros() {
let val = u24!(0x10_0000);
assert_eq!(val.leading_zeros(), 3); assert_eq!(val.trailing_zeros(), 20);
let val = u24!(0x00_0001);
assert_eq!(val.leading_zeros(), 23);
assert_eq!(val.trailing_zeros(), 0);
assert_eq!(u24::ZERO.leading_zeros(), 24);
assert_eq!(u24::ZERO.trailing_zeros(), 24);
assert_eq!(u24::MAX.leading_zeros(), 0);
assert_eq!(u24::MAX.trailing_zeros(), 0);
}
#[test]
fn test_byte_swapping() {
let val = u24::from_le_bytes([0x12, 0x34, 0x56]);
let swapped = val.swap_bytes();
assert_eq!(swapped.to_le_bytes(), [0x56, 0x34, 0x12]);
assert_eq!(u24::from_le(val), val);
assert_eq!(u24::to_le(val), val);
assert_eq!(u24::from_be(val), swapped);
assert_eq!(u24::to_be(val), swapped);
}
#[test]
fn test_comparison() {
let a = u24!(100);
let b = u24!(200);
assert!(a < b);
assert!(b > a);
assert!(a == a);
assert!(a != b);
assert!(a <= a);
assert!(a <= b);
assert!(b >= a);
assert!(b >= b);
}
#[test]
fn test_cross_type_comparison_min_max_boundaries() {
let min = u24::MIN;
let max = u24::MAX;
assert_eq!(min, u8::MIN);
assert!(max > u8::MAX);
assert_eq!(u24!(u8::MAX as u32), u8::MAX);
assert!(u24!(300) > 100u8);
assert_eq!(min, u16::MIN);
assert!(max > u16::MAX);
assert_eq!(u24!(u16::MAX as u32), u16::MAX);
assert!(u24!(70000) > 100u16);
assert_eq!(min, u32::MIN);
assert!(max < u32::MAX);
assert!(u24!(1) < u32::MAX);
assert_eq!(min, u64::MIN);
assert!(max < u64::MAX);
assert!(u24!(1) < u64::MAX);
assert_eq!(min, usize::MIN);
assert!(max < usize::MAX);
assert!(u24!(1) < usize::MAX);
assert!(min > i8::MIN);
assert!(max > i8::MAX);
assert_eq!(u24!(i8::MAX as u32), i8::MAX);
assert!(u24!(200) > i8::MAX);
assert!(min > i16::MIN);
assert!(max > i16::MAX);
assert_eq!(u24!(i16::MAX as u32), i16::MAX);
assert!(u24!(70000) > i16::MAX);
assert!(min > i32::MIN);
assert!(max < i32::MAX);
assert!(u24!(1) < i32::MAX);
assert!(min > i64::MIN);
assert!(max < i64::MAX);
assert!(u24!(1) < i64::MAX);
}
#[test]
fn test_trait_implementations() {
assert_eq!(u24::zero(), u24::ZERO);
assert!(u24::ZERO.is_zero());
assert!(!u24::ONE.is_zero());
assert_eq!(u24::one(), u24::ONE);
assert_eq!(u24::min_value(), u24::MIN);
assert_eq!(u24::max_value(), u24::MAX);
}
#[test]
fn test_string_parsing() {
let valid_cases = [
("0", 10, 0),
("1", 10, 1),
("16777215", 10, 0xFF_FFFF), ("FFFFFF", 16, 0xFF_FFFF),
("123456", 16, 0x12_3456),
("0", 16, 0),
("1000", 8, 512),
("777777", 8, 0x3F_FFF), ];
for (input, radix, expected_u32) in valid_cases {
let result = u24::from_str_radix(input, radix).unwrap();
assert_eq!(
result, expected_u32,
"Parsing '{}' with radix {} should yield {:#08X}",
input, radix, expected_u32
);
}
let invalid_cases = [
("16777216", 10, "2^24, too large"),
("1000000", 16, "> 0xFFFFFF"),
("", 10, "empty string"),
("abc", 10, "invalid digits for decimal"),
("GHI", 16, "invalid digits for hex"),
("-1", 10, "negative number"),
("18446744073709551616", 10, "way too large"),
];
for (input, radix, description) in invalid_cases {
assert!(
u24::from_str_radix(input, radix).is_err(),
"Parsing '{}' with radix {} should fail ({})",
input,
radix,
description
);
}
}
#[test]
fn test_numeric_conversions() {
let val = u24!(0x12_3456);
assert_eq!(val.to_u32(), Some(0x12_3456));
assert_eq!(val.to_u64(), Some(0x12_3456));
assert_eq!(val.to_i64(), Some(0x12_3456));
assert_eq!(val.to_u8(), None);
assert_eq!(u24::from_u64(0x12_3456), Some(val));
assert_eq!(u24::from_i64(0x12_3456), Some(val));
assert_eq!(u24::from_u64(0x01_000000), None); assert_eq!(u24::from_i64(-1), None);
assert_eq!(<u24 as NumCast>::from(0x12_3456u32), Some(val));
assert_eq!(<u24 as NumCast>::from(0x01_000000u32), None); }
#[test]
fn test_assignment_operators() {
let mut val = u24!(100);
val += u24!(50);
assert_eq!(val, 150);
val -= u24!(25);
assert_eq!(val, 125);
val *= u24!(2);
assert_eq!(val, 250);
val /= u24!(5);
assert_eq!(val, 50);
val %= u24!(7);
assert_eq!(val, 1);
}
#[test]
fn test_error_display() {
use core::error::Error;
let parse_err = u24::from_str_radix("", 10).unwrap_err();
assert!(!parse_err.to_string().is_empty());
assert!(parse_err.source().is_none());
let overflow_err = u24::from_str_radix("16777216", 10).unwrap_err();
assert_eq!(overflow_err.kind(), &IntErrorKind::PosOverflow);
}
#[test]
fn test_pow() {
let base = u24!(2);
assert_eq!(base.pow(0), 1);
assert_eq!(base.pow(1), 2);
assert_eq!(base.pow(10), 1024);
}
#[test]
#[should_panic]
fn test_pow_overflow() {
let _ = u24!(256).pow(3);
}
#[test]
#[should_panic]
fn test_must_from_u32_panic() {
u24::must_from_u32(0x01_000000); }
#[test]
fn test_must_from_u32_no_panic() {
let val = u24::must_from_u32(0xFF_FFFF);
assert_eq!(val, u24::MAX);
}
#[cfg(feature = "rangemap")]
#[test]
fn test_rangemap_step() {
use rangemap::StepLite;
let a = u24!(0);
let a = a.add_one();
let a = a.sub_one();
assert_eq!(a, u24!(0))
}
#[cfg(feature = "range-set-blaze")]
#[test]
fn test_range_set_blaze_integer_trait() {
use range_set_blaze::Integer;
assert_eq!(u24!(0).checked_add_one(), Some(u24!(1)));
assert_eq!(u24::MAX.checked_add_one(), None);
assert_eq!(u24!(0).add_one(), u24!(1));
assert_eq!(u24!(1).sub_one(), u24!(0));
let mut x = u24!(1);
x.assign_sub_one();
assert_eq!(x, u24!(0));
let mut r = u24!(0)..=u24!(2);
assert_eq!(<u24 as Integer>::safe_len(&r), 3);
assert_eq!(<u24 as Integer>::range_next(&mut r), Some(u24!(0)));
assert_eq!(<u24 as Integer>::safe_len(&r), 2);
assert_eq!(<u24 as Integer>::range_next(&mut r), Some(u24!(1)));
assert_eq!(<u24 as Integer>::safe_len(&r), 1);
assert_eq!(<u24 as Integer>::range_next(&mut r), Some(u24!(2)));
assert_eq!(<u24 as Integer>::safe_len(&r), 0);
assert_eq!(<u24 as Integer>::range_next(&mut r), None);
assert_eq!(<u24 as Integer>::range_next(&mut r), None);
let mut r = u24::MAX..=u24::MAX;
assert_eq!(<u24 as Integer>::safe_len(&r), 1);
assert_eq!(<u24 as Integer>::range_next(&mut r), Some(u24::MAX));
assert_eq!(<u24 as Integer>::safe_len(&r), 0);
assert_eq!(<u24 as Integer>::range_next(&mut r), None);
let mut r = u24!(0)..=u24!(2);
assert_eq!(<u24 as Integer>::safe_len(&r), 3);
assert_eq!(<u24 as Integer>::range_next_back(&mut r), Some(u24!(2)));
assert_eq!(<u24 as Integer>::safe_len(&r), 2);
assert_eq!(<u24 as Integer>::range_next_back(&mut r), Some(u24!(1)));
assert_eq!(<u24 as Integer>::safe_len(&r), 1);
assert_eq!(<u24 as Integer>::range_next_back(&mut r), Some(u24!(0)));
assert_eq!(<u24 as Integer>::safe_len(&r), 0);
assert_eq!(<u24 as Integer>::range_next_back(&mut r), None);
assert_eq!(<u24 as Integer>::range_next_back(&mut r), None);
let mut r = u24::MAX..=u24::MAX;
assert_eq!(<u24 as Integer>::safe_len(&r), 1);
assert_eq!(<u24 as Integer>::range_next_back(&mut r), Some(u24::MAX));
assert_eq!(<u24 as Integer>::safe_len(&r), 0);
assert_eq!(<u24 as Integer>::range_next_back(&mut r), None);
assert_eq!(<u24 as Integer>::min_value(), u24::MIN);
assert_eq!(<u24 as Integer>::max_value(), u24::MAX);
assert_eq!(<u24 as Integer>::safe_len_to_f64_lossy(123), 123f64);
assert_eq!(<u24 as Integer>::f64_to_safe_len_lossy(123.5), 123);
assert_eq!(u24!(0).inclusive_end_from_start(123), u24!(122));
assert_eq!(u24!(122).start_from_inclusive_end(123), u24!(0));
}
}