use core::{
fmt::{self, Binary, Debug, Display, LowerHex, Octal, UpperHex},
hash::{Hash, Hasher},
num::ParseIntError,
ops::{
Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Deref,
DerefMut, Div, DivAssign, Mul, MulAssign, Not, Rem, RemAssign, Shl, ShlAssign, Shr,
ShrAssign, Sub, SubAssign,
},
str::FromStr,
};
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use bytemuck::{NoUninit, Pod, Zeroable};
#[cfg(feature = "num-cast")]
use num_traits::NumCast;
use num_traits::{FromPrimitive, Num, One, ToBytes, ToPrimitive, Zero};
use crate::repr::U24Repr;
use crate::{TryFromIntError, out_of_range, u24};
#[allow(non_camel_case_types)]
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct U24(U24Repr);
unsafe impl Zeroable for U24 where U24Repr: Zeroable {}
unsafe impl NoUninit for U24 where U24Repr: NoUninit {}
impl FromPrimitive for U24
where
U24Repr: FromPrimitive,
{
#[inline]
fn from_i64(n: i64) -> Option<Self> {
U24Repr::from_i64(n).map(Self)
}
#[inline]
fn from_u64(n: u64) -> Option<Self> {
U24Repr::from_u64(n).map(Self)
}
}
impl From<U24> for u32 {
#[inline]
fn from(value: U24) -> Self {
value.to_u32()
}
}
impl From<U24> for u64 {
#[inline]
fn from(value: U24) -> Self {
<Self as From<u32>>::from(value.to_u32())
}
}
impl From<U24> for u128 {
#[inline]
fn from(value: U24) -> Self {
<Self as From<u32>>::from(value.to_u32())
}
}
impl From<U24> for usize {
#[inline]
fn from(value: U24) -> Self {
value.to_u32() as Self
}
}
impl U24 {
pub const BITS: u32 = 24;
pub const MIN: Self = u24!(U24Repr::MIN);
pub const MAX: Self = Self(unsafe { U24Repr::from_bits(U24Repr::MAX) });
pub const ZERO: Self = Self(unsafe { U24Repr::from_bits(U24Repr::ZERO) });
#[inline]
#[must_use]
pub const fn as_bits(&self) -> &u32 {
self.0.as_bits()
}
#[inline]
const fn to_bits(self) -> u32 {
self.0.to_bits()
}
#[inline]
const unsafe fn from_bits(bits: u32) -> Self {
Self(unsafe { U24Repr::from_bits(bits) })
}
#[inline]
const fn from_bits_truncate(bits: u32) -> Self {
Self(unsafe { U24Repr::from_bits(bits & U24Repr::BITS_MASK) })
}
#[inline]
#[must_use]
pub const fn to_u32(self) -> u32 {
self.0.to_u32()
}
#[inline]
#[must_use]
pub const fn wrapping_from_u32(n: u32) -> Self {
Self(U24Repr::wrapping_from_u32(n))
}
#[inline]
#[must_use]
pub const fn saturating_from_u32(n: u32) -> Self {
Self(U24Repr::saturating_from_u32(n))
}
#[inline]
#[must_use]
pub const fn swap_bytes(self) -> Self {
Self(self.0.swap_bytes())
}
#[inline]
#[must_use]
pub const fn to_le(self) -> Self {
Self(self.0.to_le())
}
#[inline]
#[must_use]
pub const fn to_be(self) -> Self {
Self(self.0.to_be())
}
#[inline]
#[must_use]
pub const fn to_ne_bytes(self) -> [u8; 3] {
self.0.to_ne_bytes()
}
#[inline]
#[must_use]
pub const fn to_le_bytes(self) -> [u8; 3] {
self.0.to_le_bytes()
}
#[inline]
#[must_use]
pub const fn to_be_bytes(self) -> [u8; 3] {
self.0.to_be_bytes()
}
#[inline]
#[must_use]
pub const fn from_ne_bytes(bytes: [u8; 3]) -> Self {
Self(U24Repr::from_ne_bytes(bytes))
}
#[inline]
#[must_use]
pub const fn from_le_bytes(bytes: [u8; 3]) -> Self {
Self(U24Repr::from_le_bytes(bytes))
}
#[inline]
#[must_use]
pub const fn from_be_bytes(bytes: [u8; 3]) -> Self {
Self(U24Repr::from_be_bytes(bytes))
}
pub fn checked_add(self, other: Self) -> Option<Self> {
self.to_u32()
.checked_add(other.to_u32())
.and_then(Self::try_from_u32)
}
pub fn checked_sub(self, other: Self) -> Option<Self> {
self.to_u32()
.checked_sub(other.to_u32())
.and_then(Self::try_from_u32)
}
pub fn checked_mul(self, other: Self) -> Option<Self> {
self.to_u32()
.checked_mul(other.to_u32())
.and_then(Self::try_from_u32)
}
pub fn checked_div(self, other: Self) -> Option<Self> {
self.to_u32()
.checked_div(other.to_u32())
.and_then(Self::try_from_u32)
}
pub fn checked_rem(self, other: Self) -> Option<Self> {
self.to_u32()
.checked_rem(other.to_u32())
.and_then(Self::try_from_u32)
}
#[inline]
#[must_use]
pub fn wrapping_add(self, rhs: Self) -> Self {
self + rhs }
#[inline]
#[must_use]
pub fn wrapping_sub(self, rhs: Self) -> Self {
self - rhs }
#[inline]
#[must_use]
pub fn wrapping_mul(self, rhs: Self) -> Self {
self * rhs }
#[inline]
#[must_use]
pub fn wrapping_div(self, rhs: Self) -> Self {
self / rhs }
#[inline]
#[must_use]
pub fn wrapping_rem(self, rhs: Self) -> Self {
self % rhs }
#[inline]
#[must_use]
pub fn saturating_add(self, rhs: Self) -> Self {
self.to_u32()
.saturating_add(rhs.to_u32())
.try_into()
.unwrap_or(Self::MAX)
}
#[inline]
#[must_use]
pub fn saturating_sub(self, rhs: Self) -> Self {
self.to_u32()
.saturating_sub(rhs.to_u32())
.try_into()
.unwrap_or(Self::MIN)
}
#[inline]
#[must_use]
pub const fn saturating_mul(self, rhs: Self) -> Self {
let result = self.to_u32().saturating_mul(rhs.to_u32());
Self::saturating_from_u32(result)
}
#[inline]
#[must_use]
pub fn saturating_div(self, rhs: Self) -> Self {
self / rhs }
#[inline]
#[must_use]
pub fn min(self, other: Self) -> Self {
if self <= other { self } else { other }
}
#[inline]
#[must_use]
pub fn max(self, other: Self) -> Self {
if self >= other { self } else { other }
}
#[inline]
#[must_use]
pub fn clamp(self, min: Self, max: Self) -> Self {
assert!(min <= max);
if self < min {
min
} else if self > max {
max
} else {
self
}
}
#[must_use]
pub const fn from_bytes_le(bytes: U24Bytes) -> Self {
bytes.to_u24_le()
}
#[must_use]
pub const fn from_bytes_be(bytes: U24Bytes) -> Self {
bytes.to_u24_be()
}
#[must_use]
pub const fn to_bytes_le(self) -> U24Bytes {
U24Bytes::from_u24_le(self)
}
#[must_use]
pub const fn to_bytes_be(self) -> U24Bytes {
U24Bytes::from_u24_be(self)
}
#[cfg(feature = "alloc")]
pub fn read_u24s_le(bytes: &[u8]) -> Option<Vec<U24>> {
if bytes.len() % 3 != 0 {
return None;
}
let mut result = Vec::with_capacity(bytes.len() / 3);
bytes.chunks_exact(3).for_each(|chunk| {
result.push(U24::from_be_bytes([chunk[0], chunk[1], chunk[2]]));
});
Some(result)
}
#[cfg(feature = "alloc")]
pub fn read_u24s_be(bytes: &[u8]) -> Option<Vec<U24>> {
if bytes.len() % 3 != 0 {
return None;
}
let mut result = Vec::with_capacity(bytes.len() / 3);
bytes.chunks_exact(3).for_each(|chunk| {
result.push(U24::from_be_bytes([chunk[0], chunk[1], chunk[2]]));
});
Some(result)
}
#[cfg(feature = "alloc")]
pub unsafe fn read_u24s_le_unchecked(bytes: &[u8]) -> Vec<U24> {
debug_assert!(bytes.len() % 3 == 0);
let chunks: &[U24Bytes] = bytemuck::cast_slice(bytes);
chunks.iter().map(|b| b.to_u24_le()).collect()
}
#[cfg(feature = "alloc")]
pub unsafe fn read_u24s_be_unchecked(bytes: &[u8]) -> Vec<U24> {
debug_assert!(bytes.len() % 3 == 0);
let chunks: &[U24Bytes] = bytemuck::cast_slice(bytes);
chunks.iter().map(|b| b.to_u24_be()).collect()
}
#[cfg(feature = "alloc")]
pub fn write_u24s_le(values: &[U24]) -> Vec<u8> {
let mut bytes = Vec::with_capacity(values.len() * 3);
for &value in values {
bytes.extend_from_slice(&value.to_le_bytes());
}
bytes
}
#[cfg(feature = "alloc")]
pub fn write_u24s_be(values: &[U24]) -> Vec<u8> {
let mut bytes = Vec::with_capacity(values.len() * 3);
for &value in values {
bytes.extend_from_slice(&value.to_be_bytes());
}
bytes
}
}
impl Not for U24 {
type Output = Self;
#[inline]
fn not(self) -> Self::Output {
let bits = !self.to_bits();
unsafe { Self::from_bits(bits & U24Repr::BITS_MASK) }
}
}
impl Not for &U24 {
type Output = U24;
#[inline]
fn not(self) -> Self::Output {
Not::not(*self)
}
}
impl Shl<u32> for U24 {
type Output = Self;
#[inline]
fn shl(self, rhs: u32) -> Self::Output {
let n = rhs % 24;
Self::from_bits_truncate(self.to_bits() << n)
}
}
impl Shl<u32> for &U24 {
type Output = U24;
#[inline]
fn shl(self, rhs: u32) -> Self::Output {
Shl::shl(*self, rhs)
}
}
impl Shr<u32> for U24 {
type Output = Self;
#[inline]
fn shr(self, rhs: u32) -> Self::Output {
let n = rhs % 24;
unsafe { Self::from_bits(self.to_bits() >> n) }
}
}
impl Shr<u32> for &U24 {
type Output = U24;
#[inline]
fn shr(self, rhs: u32) -> Self::Output {
Shr::shr(*self, rhs)
}
}
impl ShrAssign<u32> for U24 {
#[inline]
fn shr_assign(&mut self, rhs: u32) {
*self = Shr::shr(*self, rhs);
}
}
impl ShlAssign<u32> for U24 {
#[inline]
fn shl_assign(&mut self, rhs: u32) {
*self = Shl::shl(*self, rhs);
}
}
impl Hash for U24 {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.to_u32().hash(state);
}
}
macro_rules! from_str {
($meth: ident($($args: tt)*)) => {
u32::$meth($($args)*).and_then(|n| {
U24::try_from_u32(n).ok_or_else(|| {
"number too large to fit in target type"
.parse::<u32>()
.unwrap_err()
})
})
};
}
impl FromStr for U24 {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
from_str!(from_str(s))
}
}
impl ToPrimitive for U24 {
#[inline]
fn to_i64(&self) -> Option<i64> {
Some(<i64 as From<u32>>::from(Self::to_u32(*self)))
}
#[inline]
fn to_u64(&self) -> Option<u64> {
Some(<u64 as From<u32>>::from(Self::to_u32(*self)))
}
#[inline]
fn to_i32(&self) -> Option<i32> {
let val = Self::to_u32(*self);
if val > i32::MAX as u32 {
None
} else {
Some(val as i32)
}
}
#[inline]
fn to_u32(&self) -> Option<u32> {
Some(Self::to_u32(*self))
}
}
#[cfg(feature = "num-cast")]
impl NumCast for U24 {
#[inline]
fn from<T: ToPrimitive>(n: T) -> Option<Self> {
if let Some(as_i64) = n.to_i64() {
if as_i64 >= 0 {
return Self::try_from_i64(as_i64);
}
}
n.to_u64().and_then(Self::try_from_u64)
}
}
impl Zero for U24 {
#[inline]
fn zero() -> Self {
Self::ZERO
}
#[inline]
fn is_zero(&self) -> bool {
Self::ZERO == *self
}
}
impl One for U24 {
#[inline]
fn one() -> Self {
Self::wrapping_from_u32(1)
}
}
impl Num for U24 {
type FromStrRadixErr = ParseIntError;
fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
from_str!(from_str_radix(str, radix))
}
}
macro_rules! impl_U24_bin_op {
($(impl $op: ident = $assign: ident $assign_fn: ident { $($impl: tt)* })+) => {$(
impl_U24_bin_op!(impl $op = $assign $assign_fn for U24 { $($impl)* });
impl_U24_bin_op!(impl $op = $assign $assign_fn for &U24 { $($impl)* });
)+};
(impl $op: ident = $assign: ident $assign_fn: ident for $ty:ty {
fn $meth: ident($self: tt, $other: ident) {
$($impl: tt)*
}
}) => {
impl $op<$ty> for U24 {
type Output = Self;
#[inline]
fn $meth($self, $other: $ty) -> Self {
$($impl)*
}
}
impl $op<$ty> for &U24 {
type Output = U24;
#[inline]
fn $meth(self, other: $ty) -> U24 {
<U24 as $op<$ty>>::$meth(*self, other)
}
}
impl $assign<$ty> for U24 {
#[inline]
fn $assign_fn(&mut self, rhs: $ty) {
*self = $op::$meth(*self, rhs)
}
}
};
}
impl_U24_bin_op! {
impl Add = AddAssign add_assign {
fn add(self, other) {
Self::from_bits_truncate(self.to_bits().wrapping_add(other.to_bits()))
}
}
impl Sub = SubAssign sub_assign {
fn sub(self, other) {
Self::from_bits_truncate(self.to_bits().wrapping_sub(other.to_bits()))
}
}
impl Mul = MulAssign mul_assign {
fn mul(self, other) {
Self::from_bits_truncate(self.to_bits().wrapping_mul(other.to_bits()))
}
}
impl Div = DivAssign div_assign {
fn div(self, other) {
let other_val = unsafe { U24::from_bits(other.to_bits()) };
let result = <U24>::to_u32(self).wrapping_div(<U24>::to_u32(other_val));
Self::wrapping_from_u32(result)
}
}
impl Rem = RemAssign rem_assign {
fn rem(self, other) {
let other_val = unsafe { U24::from_bits(other.to_bits()) };
let result = <U24>::to_u32(self).wrapping_rem(<U24>::to_u32(other_val));
Self::wrapping_from_u32(result)
}
}
impl BitAnd = BitAndAssign bitand_assign {
fn bitand(self, rhs) {
let bits = self.to_bits() & rhs.to_bits();
unsafe { U24::from_bits(bits) }
}
}
impl BitOr = BitOrAssign bitor_assign {
fn bitor(self, rhs) {
let bits = self.to_bits() | rhs.to_bits();
unsafe { U24::from_bits(bits) }
}
}
impl BitXor = BitXorAssign bitxor_assign {
fn bitxor(self, rhs) {
let bits = self.to_bits() ^ rhs.to_bits();
unsafe { U24::from_bits(bits) }
}
}
}
macro_rules! impl_U24_fmt {
($(impl $name: path)+) => {$(
impl $name for U24 {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<u32 as $name>::fmt(&U24::to_u32(*self), f)
}
}
)*};
}
macro_rules! impl_U24_bits_fmt {
($(impl $name: path)+) => {$(
impl $name for U24 {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<u32 as $name>::fmt(&self.to_bits(), f)
}
}
)*};
}
impl_U24_fmt! {
impl Display
impl Debug
}
impl_U24_bits_fmt! {
impl UpperHex
impl LowerHex
impl Octal
impl Binary
}
impl ToBytes for U24 {
type Bytes = [u8; 3];
fn to_be_bytes(&self) -> Self::Bytes {
Self::to_be_bytes(*self)
}
fn to_le_bytes(&self) -> Self::Bytes {
Self::to_le_bytes(*self)
}
}
macro_rules! impl_from {
($($ty: ty : $func_name: ident),+ $(,)?) => {$(
impl From<$ty> for U24 {
fn from(value: $ty) -> Self {
Self::$func_name(value)
}
}
impl U24 {
#[doc = concat!("Creates a `U24` from a `", stringify!($ty), "` value.")]
pub const fn $func_name(value: $ty) -> Self {
Self(U24Repr::$func_name(value))
}
}
)+};
}
macro_rules! impl_try {
($($ty: ty : $func_name: ident),+ $(,)?) => {$(
impl TryFrom<$ty> for U24 {
type Error = TryFromIntError;
fn try_from(value: $ty) -> Result<Self, Self::Error> {
Self::$func_name(value).ok_or_else(out_of_range)
}
}
impl U24 {
#[doc = concat!("Tries to create a `U24` from a `", stringify!($ty), "` value.")]
pub const fn $func_name(value: $ty) -> Option<Self> {
match U24Repr::$func_name(value) {
Some(x) => Some(Self(x)),
None => None
}
}
}
)+};
}
impl_from! {
u8: from_u8,
u16: from_u16,
bool: from_bool,
}
impl_try! {
u32: try_from_u32,
u64: try_from_u64,
u128: try_from_u128,
i32: try_from_i32,
i64: try_from_i64,
i128: try_from_i128,
}
#[repr(transparent)]
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(
feature = "zerocopy",
derive(zerocopy::FromBytes, zerocopy::Unaligned, zerocopy::IntoBytes)
)]
pub struct U24Bytes(pub [u8; 3]);
unsafe impl Pod for U24Bytes {}
unsafe impl Zeroable for U24Bytes {}
impl U24Bytes {
#[inline]
#[must_use]
pub const fn to_u24_le(self) -> U24 {
U24::from_le_bytes(self.0)
}
#[inline]
#[must_use]
pub const fn to_u24_be(self) -> U24 {
U24::from_be_bytes(self.0)
}
#[inline]
#[must_use]
pub const fn from_u24_le(value: U24) -> Self {
Self(value.to_le_bytes())
}
#[inline]
#[must_use]
pub const fn from_u24_be(value: U24) -> Self {
Self(value.to_be_bytes())
}
#[inline]
#[must_use]
pub const fn from_bytes(bytes: [u8; 3]) -> Self {
Self(bytes)
}
#[inline]
#[must_use]
pub const fn to_bytes(self) -> [u8; 3] {
self.0
}
}
impl AsRef<[u8; 3]> for U24Bytes {
#[inline]
fn as_ref(&self) -> &[u8; 3] {
&self.0
}
}
impl AsMut<[u8; 3]> for U24Bytes {
#[inline]
fn as_mut(&mut self) -> &mut [u8; 3] {
&mut self.0
}
}
impl AsRef<[u8]> for U24Bytes {
#[inline]
fn as_ref(&self) -> &[u8] {
&self.0[..]
}
}
impl AsMut<[u8]> for U24Bytes {
#[inline]
fn as_mut(&mut self) -> &mut [u8] {
&mut self.0[..]
}
}
impl From<[u8; 3]> for U24Bytes {
#[inline]
fn from(bytes: [u8; 3]) -> Self {
Self(bytes)
}
}
impl From<U24Bytes> for [u8; 3] {
#[inline]
fn from(i24_bytes: U24Bytes) -> Self {
i24_bytes.0
}
}
impl Deref for U24Bytes {
type Target = [u8; 3];
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for U24Bytes {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[cfg(feature = "pyo3")]
mod u24_bytes_pyo3 {
use super::U24Bytes;
use pyo3::{
conversion::{FromPyObject, IntoPyObject},
prelude::*,
};
impl<'py> IntoPyObject<'py> for U24Bytes {
type Target = pyo3::types::PyBytes;
type Output = Bound<'py, pyo3::types::PyBytes>;
type Error = pyo3::PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(pyo3::types::PyBytes::new(py, &self.0))
}
}
impl<'py> IntoPyObject<'py> for &U24Bytes {
type Target = pyo3::types::PyBytes;
type Output = Bound<'py, pyo3::types::PyBytes>;
type Error = pyo3::PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(pyo3::types::PyBytes::new(py, &self.0))
}
}
impl<'a, 'py> FromPyObject<'a, 'py> for U24Bytes {
type Error = pyo3::PyErr;
fn extract(obj: pyo3::Borrowed<'a, 'py, pyo3::PyAny>) -> PyResult<Self> {
let py_bytes: &[u8] = obj.extract()?;
if py_bytes.len() != 3 {
return Err(pyo3::exceptions::PyValueError::new_err(format!(
"Expected exactly 3 bytes for U24Bytes, got {}",
py_bytes.len()
)));
}
Ok(Self([py_bytes[0], py_bytes[1], py_bytes[2]]))
}
}
}
#[cfg(feature = "serde")]
mod u24_serde {
use super::U24;
impl serde::Serialize for U24 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_u32(self.to_u32())
}
}
impl<'de> serde::Deserialize<'de> for U24 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_any(U24Visitor)
}
}
struct U24Visitor;
#[allow(unused_macros)]
macro_rules! impl_deserialize_infallible {
($([$ty: path, $visit: ident, $from: ident])+) => {$(
fn $visit<E>(self, v: $ty) -> Result<Self::Value, E> {
Ok(U24::$from(v))
}
)*};
}
macro_rules! impl_deserialize_fallible {
($([$ty: path, $visit: ident, $try_from: ident])+) => {$(
fn $visit<E>(self, v: $ty) -> Result<Self::Value, E> where E: serde::de::Error {
U24::$try_from(v).ok_or_else(|| E::custom("U24 out of range!"))
}
)*};
}
impl serde::de::Visitor<'_> for U24Visitor {
type Value = U24;
fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
formatter.write_str("an integer between 0 and 2^24-1")
}
fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E> {
Ok(U24::wrapping_from_u32(v as u32))
}
fn visit_u16<E>(self, v: u16) -> Result<Self::Value, E> {
Ok(U24::wrapping_from_u32(v as u32))
}
impl_deserialize_fallible! {
[u32, visit_u32, try_from_u32]
}
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if v > u32::MAX as u64 {
return Err(E::custom("U24 out of range"));
}
U24::try_from_u32(v as u32).ok_or_else(|| E::custom("U24 out of range"))
}
fn visit_u128<E>(self, v: u128) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if v > u32::MAX as u128 {
return Err(E::custom("U24 out of range"));
}
U24::try_from_u32(v as u32).ok_or_else(|| E::custom("U24 out of range"))
}
fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if v < 0 {
return Err(E::custom("U24 cannot represent negative values"));
}
U24::try_from_u32(v as u32).ok_or_else(|| E::custom("U24 out of range"))
}
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if v < 0 {
return Err(E::custom("U24 cannot represent negative values"));
}
if v > u32::MAX as i64 {
return Err(E::custom("U24 out of range"));
}
U24::try_from_u32(v as u32).ok_or_else(|| E::custom("U24 out of range"))
}
fn visit_i128<E>(self, v: i128) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if v < 0 {
return Err(E::custom("U24 cannot represent negative values"));
}
if v > u32::MAX as i128 {
return Err(E::custom("U24 out of range"));
}
U24::try_from_u32(v as u32).ok_or_else(|| E::custom("U24 out of range"))
}
}
}
#[cfg(feature = "ndarray")]
mod ndarray_support {
impl ndarray::ScalarOperand for crate::U24 {}
impl ndarray::ScalarOperand for crate::U24Bytes {}
}
#[cfg(feature = "pyo3")]
pub mod python {
use crate::U24;
use numpy::{Element, PyArrayDescr};
use pyo3::Py;
use pyo3::{
conversion::{FromPyObject, IntoPyObject},
exceptions::{PyOverflowError, PyValueError, PyZeroDivisionError},
prelude::*,
sync::PyOnceLock,
};
#[pyclass(name = "U24", frozen)]
pub struct PyU24 {
pub value: U24,
}
#[pymethods]
impl PyU24 {
#[new]
#[pyo3(signature = (value: "int"), text_signature = "(value: int) -> None")]
fn new(value: u32) -> PyResult<Self> {
U24::try_from_u32(value).map_or_else(
|| {
Err(PyValueError::new_err(format!(
"Value {value} is out of range for U24 (0 to 16777215)"
)))
},
|v| Ok(Self { value: v }),
)
}
#[staticmethod]
#[pyo3(signature = (bytes: "list[int]", byteorder: "Literal['little', 'big', 'native']" = "native"), text_signature = "(bytes: list[int], byteorder: Optional[Literal['little', 'big', 'native']]>) -> U24")]
fn from_bytes(bytes: &[u8], byteorder: Option<&str>) -> PyResult<Self> {
if bytes.len() != 3 {
return Err(PyValueError::new_err("bytes must be exactly 3 bytes long"));
}
let byte_array: [u8; 3] = [bytes[0], bytes[1], bytes[2]];
let value = match byteorder.unwrap_or("native") {
"little" => U24::from_le_bytes(byte_array),
"big" => U24::from_be_bytes(byte_array),
"native" => U24::from_ne_bytes(byte_array),
_ => {
return Err(PyValueError::new_err(
"byteorder must be 'little', 'big', or 'native'",
));
}
};
Ok(Self { value })
}
#[pyo3(signature = (), text_signature = "() -> int")]
const fn to_int(&self) -> u32 {
self.value.to_u32()
}
#[pyo3(signature = (byteorder: "Literal['little', 'big', 'native']" = "native"), text_signature = "(byteorder: Optional[Literal['little', 'big', 'native']]) -> list[int]")]
fn to_bytes(&self, byteorder: Option<&str>) -> PyResult<Vec<u8>> {
let bytes = match byteorder.unwrap_or("native") {
"little" => self.value.to_le_bytes(),
"big" => self.value.to_be_bytes(),
"native" => self.value.to_ne_bytes(),
_ => {
return Err(PyValueError::new_err(
"byteorder must be 'little', 'big', or 'native'",
));
}
};
Ok(bytes.to_vec())
}
fn __str__(&self) -> String {
format!("{}", self.value.to_u32())
}
fn __repr__(&self) -> String {
format!("U24({})", self.value.to_u32())
}
const fn __int__(&self) -> u32 {
self.value.to_u32()
}
fn __eq__(&self, other: &Self) -> bool {
self.value == other.value
}
fn __ne__(&self, other: &Self) -> bool {
self.value != other.value
}
fn __lt__(&self, other: &Self) -> bool {
self.value < other.value
}
fn __le__(&self, other: &Self) -> bool {
self.value <= other.value
}
fn __gt__(&self, other: &Self) -> bool {
self.value > other.value
}
fn __ge__(&self, other: &Self) -> bool {
self.value >= other.value
}
const fn __eq_int__(&self, other: u32) -> bool {
self.value.to_u32() == other
}
const fn __ne_int__(&self, other: u32) -> bool {
self.value.to_u32() != other
}
const fn __lt_int__(&self, other: u32) -> bool {
self.value.to_u32() < other
}
const fn __le_int__(&self, other: u32) -> bool {
self.value.to_u32() <= other
}
const fn __gt_int__(&self, other: u32) -> bool {
self.value.to_u32() > other
}
const fn __ge_int__(&self, other: u32) -> bool {
self.value.to_u32() >= other
}
fn __add__(&self, other: &Self) -> PyResult<Self> {
self.value.checked_add(other.value).map_or_else(
|| Err(PyOverflowError::new_err("U24 addition overflow")),
|result| Ok(Self { value: result }),
)
}
fn __sub__(&self, other: &Self) -> PyResult<Self> {
self.value.checked_sub(other.value).map_or_else(
|| Err(PyOverflowError::new_err("U24 subtraction overflow")),
|result| Ok(Self { value: result }),
)
}
fn __mul__(&self, other: &Self) -> PyResult<Self> {
self.value.checked_mul(other.value).map_or_else(
|| Err(PyOverflowError::new_err("U24 multiplication overflow")),
|result| Ok(Self { value: result }),
)
}
fn __truediv__(&self, other: &Self) -> PyResult<f64> {
if other.value.to_u32() == 0 {
return Err(PyZeroDivisionError::new_err("division by zero"));
}
Ok(f64::from(self.value.to_u32()) / f64::from(other.value.to_u32()))
}
fn __floordiv__(&self, other: &Self) -> PyResult<Self> {
self.value.checked_div(other.value).map_or_else(
|| Err(PyZeroDivisionError::new_err("division by zero")),
|result| Ok(Self { value: result }),
)
}
fn __mod__(&self, other: &Self) -> PyResult<Self> {
self.value.checked_rem(other.value).map_or_else(
|| Err(PyZeroDivisionError::new_err("division by zero")),
|result| Ok(Self { value: result }),
)
}
const fn __and__(&self, other: &Self) -> Self {
let result = self.value.to_u32() & other.value.to_u32();
Self {
value: U24::wrapping_from_u32(result),
}
}
const fn __or__(&self, other: &Self) -> Self {
let result = self.value.to_u32() | other.value.to_u32();
Self {
value: U24::wrapping_from_u32(result),
}
}
const fn __xor__(&self, other: &Self) -> Self {
let result = self.value.to_u32() ^ other.value.to_u32();
Self {
value: U24::wrapping_from_u32(result),
}
}
fn __lshift__(&self, other: u32) -> PyResult<Self> {
if other >= 32 {
return Err(PyValueError::new_err("shift count out of range"));
}
let result = self.value.to_u32() << other;
U24::try_from_u32(result).map_or_else(
|| Err(PyOverflowError::new_err("U24 left shift overflow")),
|val| Ok(Self { value: val }),
)
}
fn __rshift__(&self, other: u32) -> PyResult<Self> {
if other >= 32 {
return Err(PyValueError::new_err("shift count out of range"));
}
let result = self.value.to_u32() >> other;
Ok(Self {
value: U24::wrapping_from_u32(result),
})
}
#[classattr]
const fn min_value() -> Self {
Self { value: U24::MIN }
}
#[classattr]
const fn max_value() -> Self {
Self { value: U24::MAX }
}
#[pyo3(signature = (), text_signature = "() -> int")]
const fn count_ones(&self) -> u32 {
self.value.to_u32().count_ones()
}
#[pyo3(signature = (), text_signature = "() -> int")]
const fn count_zeros(&self) -> u32 {
self.value.to_u32().count_zeros()
}
#[pyo3(signature = (), text_signature = "() -> int")]
const fn leading_zeros(&self) -> u32 {
self.value.to_u32().leading_zeros()
}
#[pyo3(signature = (), text_signature = "() -> int")]
const fn trailing_zeros(&self) -> u32 {
self.value.to_u32().trailing_zeros()
}
#[pyo3(signature = (other: "U24"), text_signature = "(other: U24) -> Optional[U24]")]
fn checked_add(&self, other: &Self) -> Option<Self> {
self.value
.checked_add(other.value)
.map(|v| Self { value: v })
}
#[pyo3(signature = (other: "U24"), text_signature = "(other: U24) -> Optional[U24]")]
fn checked_sub(&self, other: &Self) -> Option<Self> {
self.value
.checked_sub(other.value)
.map(|v| Self { value: v })
}
#[pyo3(signature = (other: "U24"), text_signature = "(other: U24) -> Optional[U24]")]
fn checked_mul(&self, other: &Self) -> Option<Self> {
self.value
.checked_mul(other.value)
.map(|v| Self { value: v })
}
#[pyo3(signature = (other: "U24"), text_signature = "(other: U24) -> Optional[U24]")]
fn checked_div(&self, other: &Self) -> Option<Self> {
self.value
.checked_div(other.value)
.map(|v| Self { value: v })
}
#[pyo3(signature = (other: "U24"), text_signature = "(other: U24) -> Optional[U24]")]
const fn wrapping_add(&self, other: &Self) -> Self {
let result = self.value.to_u32().wrapping_add(other.value.to_u32());
Self {
value: U24::wrapping_from_u32(result),
}
}
#[pyo3(signature = (other: "U24"), text_signature = "(other: U24) -> U24")]
const fn wrapping_sub(&self, other: &Self) -> Self {
let result = self.value.to_u32().wrapping_sub(other.value.to_u32());
Self {
value: U24::wrapping_from_u32(result),
}
}
#[pyo3(signature = (other: "U24"), text_signature = "(other: U24) -> U24")]
const fn wrapping_mul(&self, other: &Self) -> Self {
let result = self.value.to_u32().wrapping_mul(other.value.to_u32());
Self {
value: U24::wrapping_from_u32(result),
}
}
#[pyo3(signature = (other: "U24"), text_signature = "(other: U24) -> U24")]
const fn saturating_add(&self, other: &Self) -> Self {
let result = self.value.to_u32().saturating_add(other.value.to_u32());
Self {
value: U24::saturating_from_u32(result),
}
}
#[pyo3(signature = (other: "U24"), text_signature = "(other: U24) -> U24")]
const fn saturating_sub(&self, other: &Self) -> Self {
let result = self.value.to_u32().saturating_sub(other.value.to_u32());
Self {
value: U24::saturating_from_u32(result),
}
}
#[pyo3(signature = (other: "U24"), text_signature = "(other: U24) -> U24")]
const fn saturating_mul(&self, other: &Self) -> Self {
let result = self.value.to_u32().saturating_mul(other.value.to_u32());
Self {
value: U24::saturating_from_u32(result),
}
}
fn __hash__(&self) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
self.value.hash(&mut hasher);
hasher.finish()
}
const fn __pos__(&self) -> Self {
Self { value: self.value }
}
fn __invert__(&self) -> Self {
let inverted = !self.value;
Self { value: inverted }
}
#[pyo3(signature = (), text_signature = "() -> int")]
const fn bit_length(&self) -> u32 {
32 - self.value.to_u32().leading_zeros()
}
#[pyo3(signature = (), text_signature = "() -> int")]
const fn bit_count(&self) -> u32 {
self.value.to_u32().count_ones()
}
#[pyo3(signature = (), text_signature = "() -> tuple[int, int]")]
const fn as_integer_ratio(&self) -> (u32, u32) {
(self.value.to_u32(), 1)
}
const fn __ceil__(&self) -> Self {
Self { value: self.value }
}
const fn __floor__(&self) -> Self {
Self { value: self.value }
}
const fn __trunc__(&self) -> Self {
Self { value: self.value }
}
}
unsafe impl Element for U24 {
const IS_COPY: bool = true;
fn clone_ref(&self, _py: Python<'_>) -> Self {
*self
}
fn get_dtype(py: Python<'_>) -> Bound<'_, PyArrayDescr> {
static DTYPE: PyOnceLock<Py<PyArrayDescr>> = PyOnceLock::new();
DTYPE
.get_or_init(py, || numpy::dtype::<u32>(py).unbind())
.clone_ref(py)
.into_bound(py)
}
}
impl<'py> IntoPyObject<'py> for U24 {
type Target = pyo3::types::PyInt;
type Output = Bound<'py, pyo3::types::PyInt>;
type Error = pyo3::PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.to_u32().into_pyobject(py)?)
}
}
impl<'py> IntoPyObject<'py> for &U24 {
type Target = pyo3::types::PyInt;
type Output = Bound<'py, pyo3::types::PyInt>;
type Error = pyo3::PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.to_u32().into_pyobject(py)?)
}
}
impl<'a, 'py> FromPyObject<'a, 'py> for U24 {
type Error = pyo3::PyErr;
fn extract(obj: pyo3::Borrowed<'a, 'py, pyo3::PyAny>) -> PyResult<Self> {
let py_int: u32 = obj.extract()?;
Self::try_from_u32(py_int).ok_or_else(|| {
pyo3::exceptions::PyOverflowError::new_err(format!(
"Value {py_int} is out of range for U24 (0 to 16777215)"
))
})
}
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "ndarray")]
#[test]
fn test_ndarray_scalar_operand() {
use ndarray::ScalarOperand;
let u24_val = crate::u24!(100);
let u24_bytes = super::U24Bytes([0x64, 0x00, 0x00]);
fn check_scalar_operand<T: ScalarOperand>(_: T) {}
check_scalar_operand(u24_val);
check_scalar_operand(u24_bytes);
}
#[cfg(feature = "num-cast")]
#[test]
fn test_num_cast_trait() {
use super::U24;
use num_traits::NumCast;
assert_eq!(
<U24 as NumCast>::from(1000u32),
Some(U24::try_from_u32(1000).unwrap())
);
assert_eq!(
<U24 as NumCast>::from(500u16),
Some(U24::try_from_u32(500).unwrap())
);
assert_eq!(
<U24 as NumCast>::from(100u8),
Some(U24::try_from_u32(100).unwrap())
);
assert_eq!(
<U24 as NumCast>::from(200i16),
Some(U24::try_from_u32(200).unwrap())
);
assert_eq!(
<U24 as NumCast>::from(50i8),
Some(U24::try_from_u32(50).unwrap())
);
assert_eq!(<U24 as NumCast>::from(20_000_000u32), None);
assert_eq!(<U24 as NumCast>::from(-100i32), None); assert_eq!(<U24 as NumCast>::from(-1i8), None);
assert_eq!(<U24 as NumCast>::from(U24::MAX.to_u32()), Some(U24::MAX));
assert_eq!(<U24 as NumCast>::from(U24::MIN.to_u32()), Some(U24::MIN));
assert_eq!(
<U24 as NumCast>::from(1000.0f32),
Some(U24::try_from_u32(1000).unwrap())
);
assert_eq!(
<U24 as NumCast>::from(500.9f32),
Some(U24::try_from_u32(500).unwrap())
); assert_eq!(<U24 as NumCast>::from(1e10f64), None); assert_eq!(<U24 as NumCast>::from(-100.0f32), None); }
}