#[cfg(test)]
use alloc::string::String;
pub const fn grow_be_int<const INPUT_SIZE: usize, const OUTPUT_SIZE: usize>(
input: [u8; INPUT_SIZE],
) -> [u8; OUTPUT_SIZE] {
debug_assert!(INPUT_SIZE <= OUTPUT_SIZE);
let mut output = if input[0] & 0b10000000 != 0 {
[0b11111111u8; OUTPUT_SIZE]
} else {
[0u8; OUTPUT_SIZE]
};
let mut i = 0;
while i < INPUT_SIZE {
output[OUTPUT_SIZE - INPUT_SIZE + i] = input[i];
i += 1;
}
output
}
pub fn shrink_be_int<const INPUT_SIZE: usize, const OUTPUT_SIZE: usize>(
input: [u8; INPUT_SIZE],
) -> Option<[u8; OUTPUT_SIZE]> {
debug_assert!(INPUT_SIZE >= OUTPUT_SIZE);
if input[0] & 0b10000000 != 0 {
for i in &input[0..(INPUT_SIZE - OUTPUT_SIZE)] {
if *i != 0b11111111u8 {
return None;
}
}
if input[INPUT_SIZE - OUTPUT_SIZE] & 0b10000000 == 0 {
return None;
}
} else {
for i in &input[0..(INPUT_SIZE - OUTPUT_SIZE)] {
if *i != 0u8 {
return None;
}
}
if input[INPUT_SIZE - OUTPUT_SIZE] & 0b10000000 != 0 {
return None;
}
}
let mut output = [0u8; OUTPUT_SIZE];
output.copy_from_slice(&input[(INPUT_SIZE - OUTPUT_SIZE)..]);
Some(output)
}
macro_rules! forward_try_from {
($input: ty, $output: ty) => {
impl TryFrom<$input> for $output {
type Error = $crate::ConversionOverflowError;
fn try_from(value: $input) -> Result<Self, Self::Error> {
value
.0
.try_into()
.map(Self)
.map_err(|_| Self::Error::new(stringify!($input), stringify!($output)))
}
}
};
}
pub(crate) use forward_try_from;
macro_rules! try_from_int_to_int {
($input: ty, $output: ty) => {
static_assertions::const_assert!(
core::mem::size_of::<$input>() > core::mem::size_of::<$output>()
);
impl TryFrom<$input> for $output {
type Error = $crate::ConversionOverflowError;
fn try_from(value: $input) -> Result<Self, Self::Error> {
$crate::math::conversion::shrink_be_int(value.to_be_bytes())
.ok_or_else(|| Self::Error::new(stringify!($input), stringify!($output)))
.map(Self::from_be_bytes)
}
}
};
}
pub(crate) use try_from_int_to_int;
macro_rules! try_from_uint_to_int {
($input: ty, $output: ty) => {
static_assertions::const_assert_eq!(stringify!($input).as_bytes()[0], b'U');
static_assertions::const_assert_eq!(stringify!($output).as_bytes()[0], b'I');
static_assertions::const_assert!(
core::mem::size_of::<$input>() >= core::mem::size_of::<$output>()
);
impl TryFrom<$input> for $output {
type Error = $crate::ConversionOverflowError;
fn try_from(value: $input) -> Result<Self, Self::Error> {
use bnum::prelude::As;
if value.0 > Self::MAX.0.as_() {
return Err(Self::Error::new(stringify!($input), stringify!($output)));
}
Ok(Self(value.0.as_()))
}
}
};
}
pub(crate) use try_from_uint_to_int;
#[cfg(test)]
pub(crate) fn test_try_from_uint_to_int<I, O>(input_type: &'static str, output_type: &'static str)
where
I: super::num_consts::NumConsts
+ From<u32>
+ Copy
+ TryFrom<O, Error = crate::ConversionOverflowError>
+ core::fmt::Debug
+ core::ops::Add<Output = I>,
O: TryFrom<I, Error = crate::ConversionOverflowError>
+ From<u32>
+ super::num_consts::NumConsts
+ core::cmp::PartialEq
+ core::fmt::Debug,
String: From<I>,
{
let v = I::MAX;
assert_eq!(
O::try_from(v),
Err(crate::ConversionOverflowError::new(input_type, output_type)),
"input::MAX value should not fit"
);
let max = I::try_from(O::MAX).unwrap();
assert_eq!(O::try_from(max), Ok(O::MAX), "output::MAX value should fit");
let v = max + I::ONE;
assert_eq!(
O::try_from(v),
Err(crate::ConversionOverflowError::new(input_type, output_type)),
"output::MAX + 1 should not fit"
);
let v = I::ZERO;
assert_eq!(O::try_from(v), Ok(O::ZERO), "zero should fit");
assert_eq!(
O::try_from(I::from(42u32)),
Ok(O::from(42u32)),
"42 should fit"
)
}
#[cfg(test)]
pub(crate) fn test_try_from_int_to_uint<I, O>(input_type: &'static str, output_type: &'static str)
where
I: super::num_consts::NumConsts
+ From<i32>
+ Copy
+ TryFrom<O>
+ core::fmt::Debug
+ core::ops::Add<Output = I>,
O: TryFrom<I, Error = crate::ConversionOverflowError>
+ From<u32>
+ super::num_consts::NumConsts
+ core::cmp::PartialEq
+ core::fmt::Debug,
String: From<I>,
<I as core::convert::TryFrom<O>>::Error: core::fmt::Debug,
{
if core::mem::size_of::<I>() <= core::mem::size_of::<O>() {
let v = I::MAX;
assert_eq!(
O::try_from(v),
Ok(O::try_from(v).unwrap()),
"input::MAX value should fit"
);
} else {
let v = I::MAX;
assert_eq!(
O::try_from(v),
Err(crate::ConversionOverflowError::new(input_type, output_type)),
"input::MAX value should not fit"
);
let max = I::try_from(O::MAX).unwrap();
assert_eq!(
O::try_from(max),
Ok(O::try_from(max).unwrap()),
"output::MAX value should fit"
);
let v = max + I::ONE;
assert_eq!(
O::try_from(v),
Err(crate::ConversionOverflowError::new(input_type, output_type)),
"output::MAX + 1 should not fit"
);
}
let v = I::from(-42i32);
assert_eq!(
O::try_from(v),
Err(crate::ConversionOverflowError::new(input_type, output_type,)),
"negative numbers should not fit"
);
let v = I::ZERO;
assert_eq!(O::try_from(v), Ok(O::ZERO), "zero should fit");
assert_eq!(
O::try_from(I::from(42i32)),
Ok(O::from(42u32)),
"42 should fit"
)
}
macro_rules! try_from_int_to_uint {
($input: ty, $output: ty) => {
static_assertions::const_assert_eq!(stringify!($input).as_bytes()[0], b'I');
static_assertions::const_assert_eq!(stringify!($output).as_bytes()[0], b'U');
impl TryFrom<$input> for $output {
type Error = ConversionOverflowError;
fn try_from(value: $input) -> Result<Self, Self::Error> {
use bnum::prelude::As;
if core::mem::size_of::<$input>() <= core::mem::size_of::<$output>() {
if value.is_negative() {
return Err(ConversionOverflowError::new(
stringify!($input),
stringify!($output),
));
}
Ok(Self(value.0.as_()))
} else {
if value.is_negative() || value.0 > <$output>::MAX.0.as_() {
return Err(ConversionOverflowError::new(
stringify!($input),
stringify!($output),
));
}
Ok(Self(value.0.as_()))
}
}
}
};
}
pub(crate) use try_from_int_to_uint;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn grow_be_int_works() {
let i32s = [i32::MIN, -1, 0, 1, 42, i32::MAX];
for i in i32s {
assert_eq!(grow_be_int(i.to_be_bytes()), (i as i64).to_be_bytes());
assert_eq!(grow_be_int(i.to_be_bytes()), (i as i128).to_be_bytes());
}
let i8s = [i8::MIN, -1, 0, 1, 42, i8::MAX];
for i in i8s {
assert_eq!(grow_be_int(i.to_be_bytes()), (i as i16).to_be_bytes());
assert_eq!(grow_be_int(i.to_be_bytes()), (i as i32).to_be_bytes());
assert_eq!(grow_be_int(i.to_be_bytes()), (i as i64).to_be_bytes());
assert_eq!(grow_be_int(i.to_be_bytes()), (i as i128).to_be_bytes());
}
}
#[test]
fn shrink_be_int_works() {
let i32s = [-42, -1, 0i32, 1, 42];
for i in i32s {
assert_eq!(
shrink_be_int(i.to_be_bytes()),
Some((i as i16).to_be_bytes())
);
assert_eq!(
shrink_be_int(i.to_be_bytes()),
Some((i as i8).to_be_bytes())
);
}
let oob = [
i32::MIN,
i32::MIN + 10,
i32::MIN + 1234,
i32::MAX - 1234,
i32::MAX - 10,
i32::MAX,
];
for i in oob {
assert_eq!(shrink_be_int::<4, 2>(i.to_be_bytes()), None);
assert_eq!(shrink_be_int::<4, 1>(i.to_be_bytes()), None);
}
for i in i16::MIN..=i16::MAX {
let cast = i as i8 as i16;
if i == cast {
assert_eq!(
shrink_be_int::<2, 1>(i.to_be_bytes()),
Some((i as i8).to_be_bytes())
);
} else {
assert_eq!(shrink_be_int::<2, 1>(i.to_be_bytes()), None);
}
}
}
}