diff --git a/src/cast.rs b/src/cast.rs index 62e6bf6..2d7fe19 100644 --- a/src/cast.rs +++ b/src/cast.rs @@ -452,6 +452,74 @@ impl NumCast for Wrapping { } } +/// A generic interface for casting between machine scalars with the +/// `as` operator, which admits narrowing and precision loss. +/// Implementers of this trait AsPrimitive should behave like a primitive +/// numeric type (e.g. a newtype around another primitive), and the +/// intended conversion must never fail. +/// +/// # Examples +/// +/// ``` +/// # use num_traits::AsPrimitive; +/// let three: i32 = (3.14159265f32).as_(); +/// assert_eq!(three, 3); +/// ``` +/// +/// # Safety +/// +/// Currently, some uses of the `as` operator are not entirely safe. +/// In particular, it is undefined behavior if: +/// +/// - A truncated floating point value cannot fit in the target integer +/// type ([#10184](https://github.com/rust-lang/rust/issues/10184)); +/// +/// ```ignore +/// # use num_traits::AsPrimitive; +/// let x: u8 = (1.04E+17).as_(); // UB +/// ``` +/// +/// - Or a floating point value does not fit in another floating +/// point type ([#15536](https://github.com/rust-lang/rust/issues/15536)). +/// +/// ```ignore +/// # use num_traits::AsPrimitive; +/// let x: f32 = (1e300f64).as_(); // UB +/// ``` +/// +pub trait AsPrimitive: 'static + Copy +where + T: 'static + Copy +{ + /// Convert a value to another, using the `as` operator. + fn as_(self) -> T; +} + +macro_rules! impl_as_primitive { + ($T: ty => $( $U: ty ),* ) => { + $( + impl AsPrimitive<$U> for $T { + #[inline] fn as_(self) -> $U { self as $U } + } + )* + }; +} + +impl_as_primitive!(u8 => char, u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64); +impl_as_primitive!(i8 => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64); +impl_as_primitive!(u16 => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64); +impl_as_primitive!(i16 => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64); +impl_as_primitive!(u32 => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64); +impl_as_primitive!(i32 => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64); +impl_as_primitive!(u64 => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64); +impl_as_primitive!(i64 => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64); +impl_as_primitive!(usize => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64); +impl_as_primitive!(isize => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64); +impl_as_primitive!(f32 => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64); +impl_as_primitive!(f64 => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64); +impl_as_primitive!(char => char, u8, i8, u16, i16, u32, i32, u64, isize, usize, i64); +impl_as_primitive!(bool => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64); + #[test] fn to_primitive_float() { use std::f32; @@ -509,3 +577,15 @@ fn wrapping_is_numcast() { fn require_numcast(_: &T) {} require_numcast(&Wrapping(42)); } + +#[test] +fn as_primitive() { + let x: f32 = (1.625f64).as_(); + assert_eq!(x, 1.625f32); + + let x: f32 = (3.14159265358979323846f64).as_(); + assert_eq!(x, 3.1415927f32); + + let x: u8 = (768i16).as_(); + assert_eq!(x, 0); +} diff --git a/src/ops/checked.rs b/src/ops/checked.rs index 45e6716..95214a2 100644 --- a/src/ops/checked.rs +++ b/src/ops/checked.rs @@ -1,4 +1,4 @@ -use std::ops::{Add, Sub, Mul, Div}; +use std::ops::{Add, Sub, Mul, Div, Shl, Shr}; /// Performs addition that returns `None` instead of wrapping around on /// overflow. @@ -90,3 +90,73 @@ checked_impl!(CheckedDiv, checked_div, i32); checked_impl!(CheckedDiv, checked_div, i64); checked_impl!(CheckedDiv, checked_div, isize); +/// Performs a left shift that returns `None` on overflow. +pub trait CheckedShl: Sized + Shl { + /// Shifts a number to the left, checking for overflow. If overflow happens, + /// `None` is returned. + /// + /// ``` + /// use num_traits::CheckedShl; + /// + /// let x: u16 = 0x0001; + /// + /// assert_eq!(CheckedShl::checked_shl(&x, 0), Some(0x0001)); + /// assert_eq!(CheckedShl::checked_shl(&x, 1), Some(0x0002)); + /// assert_eq!(CheckedShl::checked_shl(&x, 15), Some(0x8000)); + /// assert_eq!(CheckedShl::checked_shl(&x, 16), None); + /// ``` + fn checked_shl(&self, rhs: u32) -> Option; +} + +macro_rules! checked_shift_impl { + ($trait_name:ident, $method:ident, $t:ty) => { + impl $trait_name for $t { + #[inline] + fn $method(&self, rhs: u32) -> Option<$t> { + <$t>::$method(*self, rhs) + } + } + } +} + +checked_shift_impl!(CheckedShl, checked_shl, u8); +checked_shift_impl!(CheckedShl, checked_shl, u16); +checked_shift_impl!(CheckedShl, checked_shl, u32); +checked_shift_impl!(CheckedShl, checked_shl, u64); +checked_shift_impl!(CheckedShl, checked_shl, usize); + +checked_shift_impl!(CheckedShl, checked_shl, i8); +checked_shift_impl!(CheckedShl, checked_shl, i16); +checked_shift_impl!(CheckedShl, checked_shl, i32); +checked_shift_impl!(CheckedShl, checked_shl, i64); +checked_shift_impl!(CheckedShl, checked_shl, isize); + +/// Performs a right shift that returns `None` on overflow. +pub trait CheckedShr: Sized + Shr { + /// Shifts a number to the left, checking for overflow. If overflow happens, + /// `None` is returned. + /// + /// ``` + /// use num_traits::CheckedShr; + /// + /// let x: u16 = 0x8000; + /// + /// assert_eq!(CheckedShr::checked_shr(&x, 0), Some(0x8000)); + /// assert_eq!(CheckedShr::checked_shr(&x, 1), Some(0x4000)); + /// assert_eq!(CheckedShr::checked_shr(&x, 15), Some(0x0001)); + /// assert_eq!(CheckedShr::checked_shr(&x, 16), None); + /// ``` + fn checked_shr(&self, rhs: u32) -> Option; +} + +checked_shift_impl!(CheckedShr, checked_shr, u8); +checked_shift_impl!(CheckedShr, checked_shr, u16); +checked_shift_impl!(CheckedShr, checked_shr, u32); +checked_shift_impl!(CheckedShr, checked_shr, u64); +checked_shift_impl!(CheckedShr, checked_shr, usize); + +checked_shift_impl!(CheckedShr, checked_shr, i8); +checked_shift_impl!(CheckedShr, checked_shr, i16); +checked_shift_impl!(CheckedShr, checked_shr, i32); +checked_shift_impl!(CheckedShr, checked_shr, i64); +checked_shift_impl!(CheckedShr, checked_shr, isize);