numeric_cast 0.3.0

Safely cast between numbers
Documentation
use core::any::type_name;
use core::fmt;

#[inline(always)]
#[must_use]
pub fn rounding_cast<X, Y>(x: X) -> Y
where
    Y: RoundingCastFrom<X>,
{
    Y::rounding_cast_from(x)
}

pub trait RoundingCast: Sized {
    #[inline(always)]
    #[must_use]
    fn rounding_cast<T: RoundingCastFrom<Self>>(self) -> T {
        T::rounding_cast_from(self)
    }
}

macro_rules! impl_rounding_cast {
($($t:ty,)*) => {
    $(
        impl RoundingCast for $t {}
    )*
};
}

impl_rounding_cast!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64,);

pub trait RoundingCastFrom<T>: Sized {
    fn rounding_cast_from(val: T) -> Self;
}

macro_rules! rounding_cast {
    (f32 => $rhs: ty) => {
        rounding_cast!(f32 => $rhs: float);
    };
    (f64 => $rhs: ty) => {
        rounding_cast!(f64 => $rhs: float);
    };
    ($lhs: ty => $rhs:ty) => {
        impl RoundingCastFrom<$lhs> for $rhs {
            #[inline(always)]
            #[must_use]
            fn rounding_cast_from(val: $lhs) -> $rhs {
                val as $rhs
            }
        }
    };
    ($lhs: ty => $rhs: ty: inf) => {
        impl RoundingCastFrom<$lhs> for $rhs {
            #[inline(always)]
            #[must_use]
            fn rounding_cast_from(val: $lhs) -> Self {
                let ans = val as $rhs;
                if ans.is_infinite() {
                    rounding_cast_failure::<$lhs, $rhs>(val)
                }
                ans
            }
        }
    };
    ($lhs: ty => $rhs: ty: float) => {
        impl RoundingCastFrom<$lhs> for $rhs {
            #[inline(always)]
            #[must_use]
            fn rounding_cast_from(val: $lhs) -> Self {
                if val.is_nan() || val.is_infinite() {
                    rounding_cast_failure::<$lhs, $rhs>(val)
                }
                val as $rhs
            }
        }
    }
}

rounding_cast!(u8    => f32);
rounding_cast!(u16   => f32);
rounding_cast!(u32   => f32);
rounding_cast!(u64   => f32);
rounding_cast!(u128  => f32: inf);
rounding_cast!(usize => f32);
rounding_cast!(i8    => f32);
rounding_cast!(i16   => f32);
rounding_cast!(i32   => f32);
rounding_cast!(i64   => f32);
rounding_cast!(i128  => f32);
rounding_cast!(isize => f32);

rounding_cast!(u8    => f64);
rounding_cast!(u16   => f64);
rounding_cast!(u32   => f64);
rounding_cast!(u64   => f64);
rounding_cast!(u128  => f64);
rounding_cast!(usize => f64);
rounding_cast!(i8    => f64);
rounding_cast!(i16   => f64);
rounding_cast!(i32   => f64);
rounding_cast!(i64   => f64);
rounding_cast!(i128  => f64);
rounding_cast!(isize => f64);

rounding_cast!(f32 => u8 );
rounding_cast!(f32 => u16);
rounding_cast!(f32 => u32);
rounding_cast!(f32 => u64);
rounding_cast!(f32 => u128);
rounding_cast!(f32 => usize);
rounding_cast!(f32 => i8 );
rounding_cast!(f32 => i16);
rounding_cast!(f32 => i32);
rounding_cast!(f32 => i64);
rounding_cast!(f32 => i128);
rounding_cast!(f32 => isize);

rounding_cast!(f64 => u8 );
rounding_cast!(f64 => u16);
rounding_cast!(f64 => u32);
rounding_cast!(f64 => u64);
rounding_cast!(f64 => u128);
rounding_cast!(f64 => usize);
rounding_cast!(f64 => i8 );
rounding_cast!(f64 => i16);
rounding_cast!(f64 => i32);
rounding_cast!(f64 => i64);
rounding_cast!(f64 => i128);
rounding_cast!(f64 => isize);

rounding_cast!(f32 => f64);

impl RoundingCastFrom<f64> for f32 {
    #[inline(always)]
    #[must_use]
    fn rounding_cast_from(val: f64) -> Self {
        let ans = val as f32;
        if val.is_nan() || val.is_infinite() || ans.is_infinite() {
            rounding_cast_failure::<f64, f32>(val)
        }
        ans
    }
}

#[cold]
#[track_caller]
#[inline(never)]
fn rounding_cast_failure<T, U>(val: T) -> !
where
    T: fmt::Display,
{
    crate::panic_failure("rounding_cast_failure", &val, type_name::<T>(), type_name::<U>())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn normal() {
        let x: usize = usize::MAX / 2;
        let y: f64 = x.rounding_cast();
        assert_eq!(y, x as f64);

        let x: f64 = 1.0;
        let y: f32 = x.rounding_cast();
        assert_eq!(y, 1.0f32);

        let x: f32 = 300.0;
        let y: u8 = x.rounding_cast();
        assert_eq!(y, 255);
    }

    #[test]
    #[should_panic]
    fn int_overflow() {
        let x: u128 = u128::MAX;
        let _ = x.rounding_cast::<f32>();
    }

    #[test]
    #[should_panic]
    fn nan() {
        let x: f64 = f64::NAN;
        let _: f32 = rounding_cast(x);
    }

    #[test]
    #[should_panic]
    fn inf() {
        let x: f64 = f64::INFINITY;
        let _ = x.rounding_cast::<u8>();
    }
}