diff --git a/src/identities.rs b/src/identities.rs index 5c685d8..33b3818 100644 --- a/src/identities.rs +++ b/src/identities.rs @@ -17,7 +17,7 @@ pub trait Zero: Sized + Add { /// This function should return the same result at all times regardless of /// external mutable state, for example values stored in TLS or in /// `static mut`s. - // FIXME (#5527): This should be an associated constant + // This cannot be an associated constant, because of bignums. fn zero() -> Self; /// Returns `true` if `self` is equal to the additive identity. @@ -81,7 +81,7 @@ pub trait One: Sized + Mul { /// This function should return the same result at all times regardless of /// external mutable state, for example values stored in TLS or in /// `static mut`s. - // FIXME (#5527): This should be an associated constant + // This cannot be an associated constant, because of bignums. fn one() -> Self; /// Returns `true` if `self` is equal to the multiplicative identity. diff --git a/src/lib.rs b/src/lib.rs index d0b6d23..552051c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,8 +34,10 @@ pub use float::FloatConst; // pub use real::{FloatCore, Real}; // NOTE: Don't do this, it breaks `use num_traits::*;`. pub use identities::{Zero, One, zero, one}; pub use ops::inv::Inv; -pub use ops::checked::{CheckedAdd, CheckedSub, CheckedMul, CheckedDiv, CheckedShl, CheckedShr}; +pub use ops::checked::{CheckedAdd, CheckedSub, CheckedMul, CheckedDiv, + CheckedRem, CheckedNeg, CheckedShl, CheckedShr}; pub use ops::wrapping::{WrappingAdd, WrappingMul, WrappingSub}; +pub use ops::mul_add::{MulAdd, MulAddAssign}; pub use ops::saturating::Saturating; pub use sign::{Signed, Unsigned, abs, abs_sub, signum}; pub use cast::{AsPrimitive, FromPrimitive, ToPrimitive, NumCast, cast}; diff --git a/src/ops/checked.rs b/src/ops/checked.rs index 1dda911..f236770 100644 --- a/src/ops/checked.rs +++ b/src/ops/checked.rs @@ -1,4 +1,4 @@ -use core::ops::{Add, Sub, Mul, Div, Shl, Shr}; +use core::ops::{Add, Sub, Mul, Div, Rem, Shl, Shr}; /// Performs addition that returns `None` instead of wrapping around on /// overflow. @@ -106,6 +106,87 @@ checked_impl!(CheckedDiv, checked_div, isize); #[cfg(feature = "i128")] checked_impl!(CheckedDiv, checked_div, i128); +/// Performs an integral remainder that returns `None` instead of panicking on division by zero and +/// instead of wrapping around on underflow and overflow. +pub trait CheckedRem: Sized + Rem { + /// Finds the remainder of dividing two numbers, checking for underflow, overflow and division + /// by zero. If any of that happens, `None` is returned. + /// + /// # Examples + /// + /// ``` + /// use num_traits::CheckedRem; + /// use std::i32::MIN; + /// + /// assert_eq!(CheckedRem::checked_rem(&10, &7), Some(3)); + /// assert_eq!(CheckedRem::checked_rem(&10, &-7), Some(3)); + /// assert_eq!(CheckedRem::checked_rem(&-10, &7), Some(-3)); + /// assert_eq!(CheckedRem::checked_rem(&-10, &-7), Some(-3)); + /// + /// assert_eq!(CheckedRem::checked_rem(&10, &0), None); + /// + /// assert_eq!(CheckedRem::checked_rem(&MIN, &1), Some(0)); + /// assert_eq!(CheckedRem::checked_rem(&MIN, &-1), None); + /// ``` + fn checked_rem(&self, v: &Self) -> Option; +} + +checked_impl!(CheckedRem, checked_rem, u8); +checked_impl!(CheckedRem, checked_rem, u16); +checked_impl!(CheckedRem, checked_rem, u32); +checked_impl!(CheckedRem, checked_rem, u64); +checked_impl!(CheckedRem, checked_rem, usize); + +checked_impl!(CheckedRem, checked_rem, i8); +checked_impl!(CheckedRem, checked_rem, i16); +checked_impl!(CheckedRem, checked_rem, i32); +checked_impl!(CheckedRem, checked_rem, i64); +checked_impl!(CheckedRem, checked_rem, isize); + +macro_rules! checked_impl_unary { + ($trait_name:ident, $method:ident, $t:ty) => { + impl $trait_name for $t { + #[inline] + fn $method(&self) -> Option<$t> { + <$t>::$method(*self) + } + } + } +} + +/// Performs negation that returns `None` if the result can't be represented. +pub trait CheckedNeg: Sized { + /// Negates a number, returning `None` for results that can't be represented, like signed `MIN` + /// values that can't be positive, or non-zero unsigned values that can't be negative. + /// + /// # Examples + /// + /// ``` + /// use num_traits::CheckedNeg; + /// use std::i32::MIN; + /// + /// assert_eq!(CheckedNeg::checked_neg(&1_i32), Some(-1)); + /// assert_eq!(CheckedNeg::checked_neg(&-1_i32), Some(1)); + /// assert_eq!(CheckedNeg::checked_neg(&MIN), None); + /// + /// assert_eq!(CheckedNeg::checked_neg(&0_u32), Some(0)); + /// assert_eq!(CheckedNeg::checked_neg(&1_u32), None); + /// ``` + fn checked_neg(&self) -> Option; +} + +checked_impl_unary!(CheckedNeg, checked_neg, u8); +checked_impl_unary!(CheckedNeg, checked_neg, u16); +checked_impl_unary!(CheckedNeg, checked_neg, u32); +checked_impl_unary!(CheckedNeg, checked_neg, u64); +checked_impl_unary!(CheckedNeg, checked_neg, usize); + +checked_impl_unary!(CheckedNeg, checked_neg, i8); +checked_impl_unary!(CheckedNeg, checked_neg, i16); +checked_impl_unary!(CheckedNeg, checked_neg, i32); +checked_impl_unary!(CheckedNeg, checked_neg, i64); +checked_impl_unary!(CheckedNeg, checked_neg, 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, diff --git a/src/ops/mod.rs b/src/ops/mod.rs index 7b9a7e3..93b5195 100644 --- a/src/ops/mod.rs +++ b/src/ops/mod.rs @@ -2,3 +2,4 @@ pub mod saturating; pub mod checked; pub mod wrapping; pub mod inv; +pub mod mul_add; diff --git a/src/ops/mul_add.rs b/src/ops/mul_add.rs new file mode 100644 index 0000000..284ee9f --- /dev/null +++ b/src/ops/mul_add.rs @@ -0,0 +1,146 @@ +/// The fused multiply-add operation. +/// Computes (self * a) + b with only one rounding error. +/// This produces a more accurate result with better performance +/// than a separate multiplication operation followed by an add. +/// +/// Note that `A` and `B` are `Self` by default, but this is not mandatory. +/// +/// # Example +/// +/// ``` +/// use std::f32; +/// +/// let m = 10.0_f32; +/// let x = 4.0_f32; +/// let b = 60.0_f32; +/// +/// // 100.0 +/// let abs_difference = (m.mul_add(x, b) - (m*x + b)).abs(); +/// +/// assert!(abs_difference <= f32::EPSILON); +/// ``` +pub trait MulAdd { + /// The resulting type after applying the fused multiply-add. + type Output; + + /// Performs the fused multiply-add operation. + fn mul_add(self, a: A, b: B) -> Self::Output; +} + +/// The fused multiply-add assignment operation. +pub trait MulAddAssign { + /// Performs the fused multiply-add operation. + fn mul_add_assign(&mut self, a: A, b: B); +} + +#[cfg(feature = "std")] +impl MulAdd for f32 { + type Output = Self; + + #[inline] + fn mul_add(self, a: Self, b: Self) -> Self::Output { + f32::mul_add(self, a, b) + } +} + +#[cfg(feature = "std")] +impl MulAdd for f64 { + type Output = Self; + + #[inline] + fn mul_add(self, a: Self, b: Self) -> Self::Output { + f64::mul_add(self, a, b) + } +} + +macro_rules! mul_add_impl { + ($trait_name:ident for $($t:ty)*) => {$( + impl $trait_name for $t { + type Output = Self; + + #[inline] + fn mul_add(self, a: Self, b: Self) -> Self::Output { + (self * a) + b + } + } + )*} +} + +mul_add_impl!(MulAdd for isize usize i8 u8 i16 u16 i32 u32 i64 u64); + +#[cfg(feature = "std")] +impl MulAddAssign for f32 { + #[inline] + fn mul_add_assign(&mut self, a: Self, b: Self) { + *self = f32::mul_add(*self, a, b) + } +} + +#[cfg(feature = "std")] +impl MulAddAssign for f64 { + #[inline] + fn mul_add_assign(&mut self, a: Self, b: Self) { + *self = f64::mul_add(*self, a, b) + } +} + +macro_rules! mul_add_assign_impl { + ($trait_name:ident for $($t:ty)*) => {$( + impl $trait_name for $t { + #[inline] + fn mul_add_assign(&mut self, a: Self, b: Self) { + *self = (*self * a) + b + } + } + )*} +} + +mul_add_assign_impl!(MulAddAssign for isize usize i8 u8 i16 u16 i32 u32 i64 u64); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn mul_add_integer() { + macro_rules! test_mul_add { + ($($t:ident)+) => { + $( + { + let m: $t = 2; + let x: $t = 3; + let b: $t = 4; + + assert_eq!(MulAdd::mul_add(m, x, b), (m*x + b)); + } + )+ + }; + } + + test_mul_add!(usize u8 u16 u32 u64 isize i8 i16 i32 i64); + } + + #[test] + #[cfg(feature = "std")] + fn mul_add_float() { + macro_rules! test_mul_add { + ($($t:ident)+) => { + $( + { + use core::$t; + + let m: $t = 12.0; + let x: $t = 3.4; + let b: $t = 5.6; + + let abs_difference = (MulAdd::mul_add(m, x, b) - (m*x + b)).abs(); + + assert!(abs_difference <= $t::EPSILON); + } + )+ + }; + } + + test_mul_add!(f32 f64); + } +}