use typebool::{Bool, True, False};
use crate::sealed::Sealed;
#[inline(always)]
pub fn cast<F, Src, Dst>(src: Src) -> Dst
where
F: Fidelity,
Src: CastInto<Dst>,
Src::Fidelity: SufficientFor<F>,
{
src.cast_into()
}
#[inline(always)]
pub fn lossless<Src, Dst>(src: Src) -> Dst
where
Src: CastInto<Dst>,
Src::Fidelity: SufficientFor<Lossless>,
{
src.cast_into()
}
#[inline(always)]
pub fn clamping<Src, Dst>(src: Src) -> Dst
where
Src: CastInto<Dst>,
Src::Fidelity: SufficientFor<Clamping>,
{
src.cast_into()
}
#[inline(always)]
pub fn rounding<Src, Dst>(src: Src) -> Dst
where
Src: CastInto<Dst>,
Src::Fidelity: SufficientFor<Rounding>,
{
src.cast_into()
}
#[inline(always)]
pub fn lossy<Src, Dst>(src: Src) -> Dst
where
Src: CastInto<Dst>,
{
src.cast_into()
}
#[inline(always)]
pub fn is_cast_possible<F, Src, Dst>() -> bool
where
F: Fidelity,
Src: CastInto<Dst>,
Src::Fidelity: GreaterOrEqual<F>,
{
<Src::Fidelity as GreaterOrEqual<F>>::Out::VALUE
}
#[inline(always)]
pub fn try_cast<F, Src, Dst>(src: Src) -> Option<Dst>
where
F: Fidelity,
Src: CastInto<Dst>,
Src::Fidelity: GreaterOrEqual<F>,
{
if is_cast_possible::<F, Src, Dst>() {
Some(src.cast_into())
} else {
None
}
}
pub trait Fidelity:
Sealed
+ GreaterOrEqual<SameType>
+ GreaterOrEqual<Lossless>
+ GreaterOrEqual<Rounding>
+ GreaterOrEqual<Clamping>
+ GreaterOrEqual<Lossy>
{}
#[derive(Debug)]
pub enum SameType {}
impl Sealed for SameType {}
impl Fidelity for SameType {}
#[derive(Debug)]
pub enum Lossless {}
impl Sealed for Lossless {}
impl Fidelity for Lossless {}
#[derive(Debug)]
pub enum Clamping {}
impl Sealed for Clamping {}
impl Fidelity for Clamping {}
#[derive(Debug)]
pub enum Rounding {}
impl Sealed for Rounding {}
impl Fidelity for Rounding {}
#[derive(Debug)]
pub enum Lossy {}
impl Sealed for Lossy {}
impl Fidelity for Lossy {}
pub trait SufficientFor<Req: Fidelity>: Fidelity {}
impl<L: Fidelity, R: Fidelity> SufficientFor<R> for L
where
L: GreaterOrEqual<R, Out = True>,
{}
pub trait GreaterOrEqual<Rhs: Fidelity> {
type Out: Bool;
}
impl GreaterOrEqual<SameType> for SameType { type Out = True; }
impl GreaterOrEqual<Lossless> for SameType { type Out = True; }
impl GreaterOrEqual<Rounding> for SameType { type Out = True; }
impl GreaterOrEqual<Clamping> for SameType { type Out = True; }
impl GreaterOrEqual<Lossy> for SameType { type Out = True; }
impl GreaterOrEqual<SameType> for Lossless { type Out = False; }
impl GreaterOrEqual<Lossless> for Lossless { type Out = True; }
impl GreaterOrEqual<Rounding> for Lossless { type Out = True; }
impl GreaterOrEqual<Clamping> for Lossless { type Out = True; }
impl GreaterOrEqual<Lossy> for Lossless { type Out = True; }
impl GreaterOrEqual<SameType> for Rounding { type Out = False; }
impl GreaterOrEqual<Lossless> for Rounding { type Out = False; }
impl GreaterOrEqual<Rounding> for Rounding { type Out = True; }
impl GreaterOrEqual<Clamping> for Rounding { type Out = False; }
impl GreaterOrEqual<Lossy> for Rounding { type Out = True; }
impl GreaterOrEqual<SameType> for Clamping { type Out = False; }
impl GreaterOrEqual<Lossless> for Clamping { type Out = False; }
impl GreaterOrEqual<Rounding> for Clamping { type Out = False; }
impl GreaterOrEqual<Clamping> for Clamping { type Out = True; }
impl GreaterOrEqual<Lossy> for Clamping { type Out = True; }
impl GreaterOrEqual<SameType> for Lossy { type Out = False; }
impl GreaterOrEqual<Lossless> for Lossy { type Out = False; }
impl GreaterOrEqual<Rounding> for Lossy { type Out = False; }
impl GreaterOrEqual<Clamping> for Lossy { type Out = False; }
impl GreaterOrEqual<Lossy> for Lossy { type Out = True; }
pub trait CastFrom<Src> {
type Fidelity: Fidelity;
fn cast_from(src: Src) -> Self;
}
impl<T> CastFrom<T> for T {
type Fidelity = SameType;
fn cast_from(src: T) -> Self {
src
}
}
pub trait CastInto<Dst> {
type Fidelity: Fidelity;
fn cast_into(self) -> Dst;
}
impl<Src, Dst: CastFrom<Src>> CastInto<Dst> for Src {
type Fidelity = Dst::Fidelity;
fn cast_into(self) -> Dst {
Dst::cast_from(self)
}
}
macro_rules! impl_cast {
($($src:ident -> $dst:ident : $fidelity:ident $(. $direction:ident)? ,)*) => {
$(
impl CastFrom<$src> for $dst {
type Fidelity = $fidelity;
fn cast_from(src: $src) -> Self {
impl_cast!(@imp $src -> $dst : $fidelity $(. $direction)?; src)
}
}
)*
};
(@imp $src:ident -> $dst:ident : Lossless; $v:ident) => { $v.into() };
(@imp $src:ident -> $dst:ident : Rounding; $v:ident) => { $v as _ };
(@imp $src:ident -> $dst:ident : Lossy; $v:ident) => { $v as _ };
(@imp $src:ident -> $dst:ident : Clamping.pos; $v:ident) => {
if $v > $dst::max_value() as $src {
$dst::max_value()
} else {
$v as $dst
}
};
(@imp $src:ident -> $dst:ident : Clamping.neg; $v:ident) => {
if $v < $dst::min_value() as $src {
$dst::min_value()
} else {
$v as $dst
}
};
(@imp $src:ident -> $dst:ident : Clamping.both; $v:ident) => {
if $v > $dst::max_value() as $src {
$dst::max_value()
} else if $v < $dst::min_value() as $src {
$dst::min_value()
} else {
$v as $dst
}
};
}
impl_cast! {
u8 -> u16: Lossless,
u8 -> u32: Lossless,
u8 -> u64: Lossless,
u8 -> u128: Lossless,
u8 -> i8: Clamping.pos,
u8 -> i16: Lossless,
u8 -> i32: Lossless,
u8 -> i64: Lossless,
u8 -> i128: Lossless,
u8 -> f32: Lossless,
u8 -> f64: Lossless,
u16 -> u8: Clamping.pos,
u16 -> u32: Lossless,
u16 -> u64: Lossless,
u16 -> u128: Lossless,
u16 -> i8: Clamping.pos,
u16 -> i16: Clamping.pos,
u16 -> i32: Lossless,
u16 -> i64: Lossless,
u16 -> i128: Lossless,
u16 -> f32: Lossless,
u16 -> f64: Lossless,
u32 -> u8: Clamping.pos,
u32 -> u16: Clamping.pos,
u32 -> u64: Lossless,
u32 -> u128: Lossless,
u32 -> i8: Clamping.pos,
u32 -> i16: Clamping.pos,
u32 -> i32: Clamping.pos,
u32 -> i64: Lossless,
u32 -> i128: Lossless,
u32 -> f32: Rounding,
u32 -> f64: Lossless,
u64 -> u8: Clamping.pos,
u64 -> u16: Clamping.pos,
u64 -> u32: Clamping.pos,
u64 -> u128: Lossless,
u64 -> i8: Clamping.pos,
u64 -> i16: Clamping.pos,
u64 -> i32: Clamping.pos,
u64 -> i64: Clamping.pos,
u64 -> i128: Lossless,
u64 -> f32: Rounding,
u64 -> f64: Rounding,
u128 -> u8: Clamping.pos,
u128 -> u16: Clamping.pos,
u128 -> u32: Clamping.pos,
u128 -> u64: Clamping.pos,
u128 -> i8: Clamping.pos,
u128 -> i16: Clamping.pos,
u128 -> i32: Clamping.pos,
u128 -> i64: Clamping.pos,
u128 -> i128: Clamping.pos,
u128 -> f32: Lossy,
u128 -> f64: Rounding,
i8 -> u8: Clamping.neg,
i8 -> u16: Clamping.neg,
i8 -> u32: Clamping.neg,
i8 -> u64: Clamping.neg,
i8 -> u128: Clamping.neg,
i8 -> i16: Lossless,
i8 -> i32: Lossless,
i8 -> i64: Lossless,
i8 -> i128: Lossless,
i8 -> f32: Lossless,
i8 -> f64: Lossless,
i16 -> u8: Clamping.both,
i16 -> u16: Clamping.neg,
i16 -> u32: Clamping.neg,
i16 -> u64: Clamping.neg,
i16 -> u128: Clamping.neg,
i16 -> i8: Clamping.both,
i16 -> i32: Lossless,
i16 -> i64: Lossless,
i16 -> i128: Lossless,
i16 -> f32: Lossless,
i16 -> f64: Lossless,
i32 -> u8: Clamping.both,
i32 -> u16: Clamping.both,
i32 -> u32: Clamping.neg,
i32 -> u64: Clamping.neg,
i32 -> u128: Clamping.neg,
i32 -> i8: Clamping.both,
i32 -> i16: Clamping.both,
i32 -> i64: Lossless,
i32 -> i128: Lossless,
i32 -> f32: Rounding,
i32 -> f64: Lossless,
i64 -> u8: Clamping.both,
i64 -> u16: Clamping.both,
i64 -> u32: Clamping.both,
i64 -> u64: Clamping.neg,
i64 -> u128: Clamping.neg,
i64 -> i8: Clamping.both,
i64 -> i16: Clamping.both,
i64 -> i32: Clamping.both,
i64 -> i128: Lossless,
i64 -> f32: Rounding,
i64 -> f64: Rounding,
i128 -> u8: Clamping.both,
i128 -> u16: Clamping.both,
i128 -> u32: Clamping.both,
i128 -> u64: Clamping.both,
i128 -> u128: Clamping.neg,
i128 -> i8: Clamping.both,
i128 -> i16: Clamping.both,
i128 -> i32: Clamping.both,
i128 -> i64: Clamping.both,
i128 -> f32: Lossy,
i128 -> f64: Rounding,
f32 -> u8: Lossy,
f32 -> u16: Lossy,
f32 -> u32: Lossy,
f32 -> u64: Lossy,
f32 -> u128: Lossy,
f32 -> i8: Lossy,
f32 -> i16: Lossy,
f32 -> i32: Lossy,
f32 -> i64: Lossy,
f32 -> i128: Rounding,
f32 -> f64: Lossless,
f64 -> u8: Lossy,
f64 -> u16: Lossy,
f64 -> u32: Lossy,
f64 -> u64: Lossy,
f64 -> u128: Lossy,
f64 -> i8: Lossy,
f64 -> i16: Lossy,
f64 -> i32: Lossy,
f64 -> i64: Lossy,
f64 -> i128: Lossy,
f64 -> f32: Lossy,
}
#[cfg(test)]
mod tests {
use super::*;
#[inline(never)]
fn check<F, SrcT, DstT>(
src: SrcT,
dst: DstT,
should_succeed: bool,
rigor_str: &str,
src_str: &str,
dst_str: &str,
)
where
F: Fidelity,
SrcT: Copy,
DstT: PartialEq + Copy + CastFrom<SrcT>,
DstT::Fidelity: GreaterOrEqual<F>,
{
let (expected_val, expected_str, actual_str) = if should_succeed {
(Some(dst), "succeed", "failed")
} else {
(None, "fail", "succeeded")
};
if try_cast::<F, SrcT, DstT>(src) != expected_val {
panic!(
"expected {} -> {} `try_cast` to {}, but it {}",
src_str,
dst_str,
expected_str,
actual_str,
);
}
if is_cast_possible::<F, SrcT, DstT>() != should_succeed {
panic!(
"expected `is_cast_possible<{}, {}, {}>()` to be `{}`, but \
it returned `{}`",
rigor_str,
src_str,
dst_str,
should_succeed,
!should_succeed,
);
}
}
macro_rules! test {
(
$fun:ident, $rigor:ident: $ty:ident =>
$u8:tt $u16:tt $u32:tt $u64:tt $u128:tt
$i8:tt $i16:tt $i32:tt $i64:tt $i128:tt
$f32:tt $f64:tt
) => {{
test!(@check $rigor: $ty as u8 => $u8);
test!(@check $rigor: $ty as u16 => $u16);
test!(@check $rigor: $ty as u32 => $u32);
test!(@check $rigor: $ty as u64 => $u64);
test!(@check $rigor: $ty as u128 => $u128);
test!(@check $rigor: $ty as i8 => $i8);
test!(@check $rigor: $ty as i16 => $i16);
test!(@check $rigor: $ty as i32 => $i32);
test!(@check $rigor: $ty as i64 => $i64);
test!(@check $rigor: $ty as i128 => $i128);
test!(@check $rigor: $ty as f32 => $f32);
test!(@check $rigor: $ty as f64 => $f64);
}};
(@check $rigor:ident: $src:ident as $dst:ident => $outcome:tt) => {
check::<$rigor, $src, $dst>(
test!(@lit $src),
test!(@lit $dst),
test!(@to_bool $outcome),
stringify!($rigor),
stringify!($src),
stringify!($dst),
)
};
(@to_bool n) => { false };
(@to_bool y) => { true };
(@lit f32) => { 3.0 };
(@lit f64) => { 3.0 };
(@lit $integer_ty:ident) => { 3 };
}
#[test]
fn cast_try_no_cast() {
test!(no_cast, SameType: u8 => y n n n n n n n n n n n);
test!(no_cast, SameType: u16 => n y n n n n n n n n n n);
test!(no_cast, SameType: u32 => n n y n n n n n n n n n);
test!(no_cast, SameType: u64 => n n n y n n n n n n n n);
test!(no_cast, SameType: u128 => n n n n y n n n n n n n);
test!(no_cast, SameType: i8 => n n n n n y n n n n n n);
test!(no_cast, SameType: i16 => n n n n n n y n n n n n);
test!(no_cast, SameType: i32 => n n n n n n n y n n n n);
test!(no_cast, SameType: i64 => n n n n n n n n y n n n);
test!(no_cast, SameType: i128 => n n n n n n n n n y n n);
test!(no_cast, SameType: f32 => n n n n n n n n n n y n);
test!(no_cast, SameType: f64 => n n n n n n n n n n n y);
}
#[test]
fn cast_try_lossless() {
test!(lossless, Lossless: u8 => y y y y y n y y y y y y);
test!(lossless, Lossless: u16 => n y y y y n n y y y y y);
test!(lossless, Lossless: u32 => n n y y y n n n y y n y);
test!(lossless, Lossless: u64 => n n n y y n n n n y n n);
test!(lossless, Lossless: u128 => n n n n y n n n n n n n);
test!(lossless, Lossless: i8 => n n n n n y y y y y y y);
test!(lossless, Lossless: i16 => n n n n n n y y y y y y);
test!(lossless, Lossless: i32 => n n n n n n n y y y n y);
test!(lossless, Lossless: i64 => n n n n n n n n y y n n);
test!(lossless, Lossless: i128 => n n n n n n n n n y n n);
test!(lossless, Lossless: f32 => n n n n n n n n n n y y);
test!(lossless, Lossless: f64 => n n n n n n n n n n n y);
}
#[test]
fn cast_try_clamping() {
test!(clamping, Clamping: u8 => y y y y y y y y y y y y);
test!(clamping, Clamping: u16 => y y y y y y y y y y y y);
test!(clamping, Clamping: u32 => y y y y y y y y y y n y);
test!(clamping, Clamping: u64 => y y y y y y y y y y n n);
test!(clamping, Clamping: u128 => y y y y y y y y y y n n);
test!(clamping, Clamping: i8 => y y y y y y y y y y y y);
test!(clamping, Clamping: i16 => y y y y y y y y y y y y);
test!(clamping, Clamping: i32 => y y y y y y y y y y n y);
test!(clamping, Clamping: i64 => y y y y y y y y y y n n);
test!(clamping, Clamping: i128 => y y y y y y y y y y n n);
test!(clamping, Clamping: f32 => n n n n n n n n n n y y);
test!(clamping, Clamping: f64 => n n n n n n n n n n n y);
}
#[test]
fn cast_try_rounding() {
test!(rounding, Rounding: u8 => y y y y y n y y y y y y);
test!(rounding, Rounding: u16 => n y y y y n n y y y y y);
test!(rounding, Rounding: u32 => n n y y y n n n y y y y);
test!(rounding, Rounding: u64 => n n n y y n n n n y y y);
test!(rounding, Rounding: u128 => n n n n y n n n n n n y);
test!(rounding, Rounding: i8 => n n n n n y y y y y y y);
test!(rounding, Rounding: i16 => n n n n n n y y y y y y);
test!(rounding, Rounding: i32 => n n n n n n n y y y y y);
test!(rounding, Rounding: i64 => n n n n n n n n y y y y);
test!(rounding, Rounding: i128 => n n n n n n n n n y n y);
test!(rounding, Rounding: f32 => n n n n n n n n n y y y);
test!(rounding, Rounding: f64 => n n n n n n n n n n n y);
}
#[test]
fn cast_try_lossy() {
test!(lossy, Lossy: u8 => y y y y y y y y y y y y);
test!(lossy, Lossy: u16 => y y y y y y y y y y y y);
test!(lossy, Lossy: u32 => y y y y y y y y y y y y);
test!(lossy, Lossy: u64 => y y y y y y y y y y y y);
test!(lossy, Lossy: u128 => y y y y y y y y y y y y);
test!(lossy, Lossy: i8 => y y y y y y y y y y y y);
test!(lossy, Lossy: i16 => y y y y y y y y y y y y);
test!(lossy, Lossy: i32 => y y y y y y y y y y y y);
test!(lossy, Lossy: i64 => y y y y y y y y y y y y);
test!(lossy, Lossy: i128 => y y y y y y y y y y y y);
test!(lossy, Lossy: f32 => y y y y y y y y y y y y);
test!(lossy, Lossy: f64 => y y y y y y y y y y y y);
}
#[test]
fn cast_clamping() {
assert_eq!(clamping::<u16, u8>(255), 255);
assert_eq!(clamping::<u16, u8>(256), 255);
assert_eq!(clamping::<u16, u8>(20_000), 255);
assert_eq!(clamping::<i16, u8>(255), 255);
assert_eq!(clamping::<i16, u8>(256), 255);
assert_eq!(clamping::<i16, u8>(20_000), 255);
assert_eq!(clamping::<i16, u8>(0), 0);
assert_eq!(clamping::<i16, u8>(-1), 0);
assert_eq!(clamping::<i16, u8>(-10_000), 0);
assert_eq!(clamping::<u16, i8>(127), 127);
assert_eq!(clamping::<u16, i8>(128), 127);
assert_eq!(clamping::<u16, i8>(20_000), 127);
assert_eq!(clamping::<u8, i8>(127), 127);
assert_eq!(clamping::<u8, i8>(128), 127);
assert_eq!(clamping::<i16, i8>(127), 127);
assert_eq!(clamping::<i16, i8>(128), 127);
assert_eq!(clamping::<i16, i8>(20_000), 127);
assert_eq!(clamping::<i16, i8>(-128), -128);
assert_eq!(clamping::<i16, i8>(-129), -128);
assert_eq!(clamping::<i16, i8>(-20_000), -128);
}
#[test]
fn cast_lossy() {
assert_eq!(lossy::<f32, i8>(0.0), 0);
assert_eq!(lossy::<f32, i8>(1.0), 1);
assert_eq!(lossy::<f32, i8>(-1.0), -1);
assert!(lossy::<f32, i8>(1.5) == 1 || lossy::<f32, i8>(1.5) == 2);
assert!(lossy::<f32, i8>(-1.5) == -1 || lossy::<f32, i8>(-1.5) == -2);
}
}