From 35b93556e4cb2ea854a09e39d9a2bbe93cf2328d Mon Sep 17 00:00:00 2001 From: Mikolaj Wielgus Date: Tue, 1 Sep 2026 21:30:09 +0200 Subject: [PATCH] Add full support for `Saturating` closely matching that of `Wrapping` The library had only partial support for the standard library's `Saturating` type wrapper, implementing `Num`, `Zero`, `ConstZero`, `One`, `ConstOne` traits but not other ones. So, I have added implementations for `Saturating` of the remaining traits: `Signed`, `Unsigned`, `Bounded`, `ToPrimitive`, `FromPrimitive`, `NumCast`, `SaturatingAdd`, `SaturatingSub`, `SaturatingMul`, `Pow`. --- src/bounds.rs | 48 +++++++++++++++++++++++++++++++++ src/cast.rs | 63 ++++++++++++++++++++++++++++++++++++++----- src/ops/saturating.rs | 51 +++++++++++++++++++++++++++++++++++ src/pow.rs | 25 +++++++++++++++++ src/sign.rs | 50 ++++++++++++++++++++++++++++++++++ tests/cast.rs | 56 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 287 insertions(+), 6 deletions(-) diff --git a/src/bounds.rs b/src/bounds.rs index 4c918170..d949e453 100644 --- a/src/bounds.rs +++ b/src/bounds.rs @@ -124,6 +124,16 @@ impl Bounded for Wrapping { } } +#[cfg(has_num_saturating)] +impl Bounded for core::num::Saturating { + fn min_value() -> Self { + core::num::Saturating(T::min_value()) + } + fn max_value() -> Self { + core::num::Saturating(T::max_value()) + } +} + bounded_impl!(f32, f32::MIN, f32::MAX); macro_rules! for_each_tuple_ { @@ -194,6 +204,44 @@ fn wrapping_is_bounded() { require_bounded(&Wrapping(-42)); } +#[test] +#[cfg(has_num_saturating)] +fn saturating_bounded() { + macro_rules! test_saturating_bounded { + ($($t:ty)+) => { + $( + assert_eq!( as Bounded>::min_value().0, <$t>::min_value()); + assert_eq!( as Bounded>::max_value().0, <$t>::max_value()); + )+ + }; + } + + test_saturating_bounded!(usize u8 u16 u32 u64 isize i8 i16 i32 i64); +} + +#[test] +#[cfg(has_num_saturating)] +fn saturating_bounded_i128() { + macro_rules! test_saturating_bounded { + ($($t:ty)+) => { + $( + assert_eq!( as Bounded>::min_value().0, <$t>::min_value()); + assert_eq!( as Bounded>::max_value().0, <$t>::max_value()); + )+ + }; + } + + test_saturating_bounded!(u128 i128); +} + +#[test] +#[cfg(has_num_saturating)] +fn saturating_is_bounded() { + fn require_bounded(_: &T) {} + require_bounded(&core::num::Saturating(42_u32)); + require_bounded(&core::num::Saturating(-42)); +} + #[test] fn bounded_unsigned_nonzero() { macro_rules! test_bounded_impl_unsigned_nonzero { diff --git a/src/cast.rs b/src/cast.rs index 7e5fd3a2..8eb50a4a 100644 --- a/src/cast.rs +++ b/src/cast.rs @@ -695,7 +695,7 @@ impl_from_primitive_nonzero!(NonZeroU32, to_u32); impl_from_primitive_nonzero!(NonZeroU64, to_u64); impl_from_primitive_nonzero!(NonZeroU128, to_u128); -macro_rules! impl_to_primitive_wrapping { +macro_rules! impl_to_primitive_for_wrapper { ($( fn $method:ident -> $i:ident ; )*) => {$( #[inline] fn $method(&self) -> Option<$i> { @@ -705,7 +705,7 @@ macro_rules! impl_to_primitive_wrapping { } impl ToPrimitive for Wrapping { - impl_to_primitive_wrapping! { + impl_to_primitive_for_wrapper! { fn to_isize -> isize; fn to_i8 -> i8; fn to_i16 -> i16; @@ -725,17 +725,39 @@ impl ToPrimitive for Wrapping { } } -macro_rules! impl_from_primitive_wrapping { - ($( fn $method:ident ( $i:ident ); )*) => {$( +macro_rules! impl_from_primitive_for_wrapper { + ($wrapper:path, $( fn $method:ident ( $i:ident ); )*) => {$( #[inline] fn $method(n: $i) -> Option { - T::$method(n).map(Wrapping) + T::$method(n).map($wrapper) } )*} } impl FromPrimitive for Wrapping { - impl_from_primitive_wrapping! { + impl_from_primitive_for_wrapper! { Wrapping, + fn from_isize(isize); + fn from_i8(i8); + fn from_i16(i16); + fn from_i32(i32); + fn from_i64(i64); + fn from_i128(i128); + + fn from_usize(usize); + fn from_u8(u8); + fn from_u16(u16); + fn from_u32(u32); + fn from_u64(u64); + fn from_u128(u128); + + fn from_f32(f32); + fn from_f64(f64); + } +} + +#[cfg(has_num_saturating)] +impl FromPrimitive for core::num::Saturating { + impl_from_primitive_for_wrapper! { core::num::Saturating, fn from_isize(isize); fn from_i8(i8); fn from_i16(i16); @@ -844,6 +866,35 @@ impl NumCast for Wrapping { } } +#[cfg(has_num_saturating)] +impl ToPrimitive for core::num::Saturating { + impl_to_primitive_for_wrapper! { + fn to_isize -> isize; + fn to_i8 -> i8; + fn to_i16 -> i16; + fn to_i32 -> i32; + fn to_i64 -> i64; + fn to_i128 -> i128; + + fn to_usize -> usize; + fn to_u8 -> u8; + fn to_u16 -> u16; + fn to_u32 -> u32; + fn to_u64 -> u64; + fn to_u128 -> u128; + + fn to_f32 -> f32; + fn to_f64 -> f64; + } +} + +#[cfg(has_num_saturating)] +impl NumCast for core::num::Saturating { + fn from(n: U) -> Option { + T::from(n).map(core::num::Saturating) + } +} + /// 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 diff --git a/src/ops/saturating.rs b/src/ops/saturating.rs index 16a00457..cc2d54c2 100644 --- a/src/ops/saturating.rs +++ b/src/ops/saturating.rs @@ -107,6 +107,36 @@ saturating_impl!(SaturatingMul, saturating_mul, i128); // TODO: add SaturatingNeg for signed integer primitives once the saturating_neg() API is stable. +#[cfg(has_num_saturating)] +impl SaturatingAdd for core::num::Saturating +where + core::num::Saturating: Add>, +{ + fn saturating_add(&self, v: &Self) -> Self { + core::num::Saturating(self.0.saturating_add(&v.0)) + } +} + +#[cfg(has_num_saturating)] +impl SaturatingSub for core::num::Saturating +where + core::num::Saturating: Sub>, +{ + fn saturating_sub(&self, v: &Self) -> Self { + core::num::Saturating(self.0.saturating_sub(&v.0)) + } +} + +#[cfg(has_num_saturating)] +impl SaturatingMul for core::num::Saturating +where + core::num::Saturating: Mul>, +{ + fn saturating_mul(&self, v: &Self) -> Self { + core::num::Saturating(self.0.saturating_mul(&v.0)) + } +} + #[test] fn test_saturating_traits() { fn saturating_add(a: T, b: T) -> T { @@ -128,3 +158,24 @@ fn test_saturating_traits() { assert_eq!(saturating_mul(127, 2), 127i8); assert_eq!(saturating_mul(-128, 2), -128i8); } + +#[test] +#[cfg(has_num_saturating)] +fn saturating_is_saturatingadd() { + fn require_saturatingadd(_: &T) {} + require_saturatingadd(&core::num::Saturating(42)); +} + +#[test] +#[cfg(has_num_saturating)] +fn saturating_is_saturatingsub() { + fn require_saturatingsub(_: &T) {} + require_saturatingsub(&core::num::Saturating(42)); +} + +#[test] +#[cfg(has_num_saturating)] +fn saturating_is_saturatingmul() { + fn require_saturatingmul(_: &T) {} + require_saturatingmul(&core::num::Saturating(42)); +} diff --git a/src/pow.rs b/src/pow.rs index ef51c954..98976177 100644 --- a/src/pow.rs +++ b/src/pow.rs @@ -130,6 +130,31 @@ pow_impl!(Wrapping); pow_impl!(Wrapping); pow_impl!(Wrapping); +#[cfg(has_num_saturating)] +pow_impl!(core::num::Saturating); +#[cfg(has_num_saturating)] +pow_impl!(core::num::Saturating); +#[cfg(has_num_saturating)] +pow_impl!(core::num::Saturating); +#[cfg(has_num_saturating)] +pow_impl!(core::num::Saturating); +#[cfg(has_num_saturating)] +pow_impl!(core::num::Saturating); +#[cfg(has_num_saturating)] +pow_impl!(core::num::Saturating); +#[cfg(has_num_saturating)] +pow_impl!(core::num::Saturating); +#[cfg(has_num_saturating)] +pow_impl!(core::num::Saturating); +#[cfg(has_num_saturating)] +pow_impl!(core::num::Saturating); +#[cfg(has_num_saturating)] +pow_impl!(core::num::Saturating); +#[cfg(has_num_saturating)] +pow_impl!(core::num::Saturating); +#[cfg(has_num_saturating)] +pow_impl!(core::num::Saturating); + // FIXME: these should be possible // pow_impl!(u8, u64); // pow_impl!(i16, u64); diff --git a/src/sign.rs b/src/sign.rs index a0d6b0fd..dcf05658 100644 --- a/src/sign.rs +++ b/src/sign.rs @@ -1,3 +1,5 @@ +#[cfg(has_num_saturating)] +use core::num::Saturating; use core::num::Wrapping; use core::ops::Neg; @@ -104,6 +106,37 @@ where } } +#[cfg(has_num_saturating)] +impl Signed for Saturating +where + Saturating: Num + Neg>, +{ + #[inline] + fn abs(&self) -> Self { + Saturating(self.0.abs()) + } + + #[inline] + fn abs_sub(&self, other: &Self) -> Self { + Saturating(self.0.abs_sub(&other.0)) + } + + #[inline] + fn signum(&self) -> Self { + Saturating(self.0.signum()) + } + + #[inline] + fn is_positive(&self) -> bool { + self.0.is_positive() + } + + #[inline] + fn is_negative(&self) -> bool { + self.0.is_negative() + } +} + macro_rules! signed_float_impl { ($t:ty) => { impl Signed for $t { @@ -203,6 +236,9 @@ empty_trait_impl!(Unsigned for usize u8 u16 u32 u64 u128); impl Unsigned for Wrapping where Wrapping: Num {} +#[cfg(has_num_saturating)] +impl Unsigned for Saturating where Saturating: Num {} + #[test] fn unsigned_wrapping_is_unsigned() { fn require_unsigned(_: &T) {} @@ -214,3 +250,17 @@ fn signed_wrapping_is_signed() { fn require_signed(_: &T) {} require_signed(&Wrapping(-42)); } + +#[test] +#[cfg(has_num_saturating)] +fn unsigned_saturating_is_unsigned() { + fn require_unsigned(_: &T) {} + require_unsigned(&Saturating(42_u32)); +} + +#[test] +#[cfg(has_num_saturating)] +fn signed_saturating_is_signed() { + fn require_signed(_: &T) {} + require_signed(&Saturating(-42)); +} diff --git a/tests/cast.rs b/tests/cast.rs index 785d760e..97fa923f 100644 --- a/tests/cast.rs +++ b/tests/cast.rs @@ -73,6 +73,62 @@ fn wrapping_is_numcast() { require_numcast(&Wrapping(42)); } +#[test] +#[cfg(has_num_saturating)] +fn saturating_to_primitive() { + use core::num::Saturating; + + macro_rules! test_saturating_to_primitive { + ($($t:ty)+) => { + $({ + let i: $t = 0; + let w = Saturating(i); + assert_eq!(i.to_u8(), w.to_u8()); + assert_eq!(i.to_u16(), w.to_u16()); + assert_eq!(i.to_u32(), w.to_u32()); + assert_eq!(i.to_u64(), w.to_u64()); + assert_eq!(i.to_usize(), w.to_usize()); + assert_eq!(i.to_i8(), w.to_i8()); + assert_eq!(i.to_i16(), w.to_i16()); + assert_eq!(i.to_i32(), w.to_i32()); + assert_eq!(i.to_i64(), w.to_i64()); + assert_eq!(i.to_isize(), w.to_isize()); + assert_eq!(i.to_f32(), w.to_f32()); + assert_eq!(i.to_f64(), w.to_f64()); + })+ + }; + } + + test_saturating_to_primitive!(usize u8 u16 u32 u64 isize i8 i16 i32 i64); +} + +#[test] +#[cfg(has_num_saturating)] +fn saturating_is_toprimitive() { + use core::num::Saturating; + + fn require_toprimitive(_: &T) {} + require_toprimitive(&Saturating(42)); +} + +#[test] +#[cfg(has_num_saturating)] +fn saturating_is_fromprimitive() { + use core::num::Saturating; + + fn require_fromprimitive(_: &T) {} + require_fromprimitive(&Saturating(42)); +} + +#[test] +#[cfg(has_num_saturating)] +fn saturating_is_numcast() { + use core::num::Saturating; + + fn require_numcast(_: &T) {} + require_numcast(&Saturating(42)); +} + #[test] fn as_primitive() { let x: f32 = (1.625f64).as_();