diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs index e7101537b298f..f51b561cabf87 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -1276,6 +1276,38 @@ impl f128 { self } + /// Clamps this number to a symmetric range centered around zero. + /// + /// The method clamps the number's magnitude (absolute value) to be at most `limit`. + /// + /// This is functionally equivalent to `self.clamp(-limit, limit)`, but is more + /// explicit about the intent. + /// + /// # Panics + /// + /// Panics if `limit` is negative or NaN, as this indicates a logic error. + /// + /// # Examples + /// + /// ``` + /// #![feature(f128)] + /// #![feature(clamp_magnitude)] + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { + /// assert_eq!(5.0f128.clamp_magnitude(3.0), 3.0); + /// assert_eq!((-5.0f128).clamp_magnitude(3.0), -3.0); + /// assert_eq!(2.0f128.clamp_magnitude(3.0), 2.0); + /// assert_eq!((-2.0f128).clamp_magnitude(3.0), -2.0); + /// # } + /// ``` + #[inline] + #[unstable(feature = "clamp_magnitude", issue = "148519")] + #[must_use = "this returns the clamped value and does not modify the original"] + pub fn clamp_magnitude(self, limit: f128) -> f128 { + assert!(limit >= 0.0, "limit must be non-negative"); + let limit = limit.abs(); // Canonicalises -0.0 to 0.0 + self.clamp(-limit, limit) + } + /// Computes the absolute value of `self`. /// /// This function always returns the precise result. diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs index aa8342a22ad58..318c9599ccea1 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -1254,6 +1254,38 @@ impl f16 { self } + /// Clamps this number to a symmetric range centered around zero. + /// + /// The method clamps the number's magnitude (absolute value) to be at most `limit`. + /// + /// This is functionally equivalent to `self.clamp(-limit, limit)`, but is more + /// explicit about the intent. + /// + /// # Panics + /// + /// Panics if `limit` is negative or NaN, as this indicates a logic error. + /// + /// # Examples + /// + /// ``` + /// #![feature(f16)] + /// #![feature(clamp_magnitude)] + /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] { + /// assert_eq!(5.0f16.clamp_magnitude(3.0), 3.0); + /// assert_eq!((-5.0f16).clamp_magnitude(3.0), -3.0); + /// assert_eq!(2.0f16.clamp_magnitude(3.0), 2.0); + /// assert_eq!((-2.0f16).clamp_magnitude(3.0), -2.0); + /// # } + /// ``` + #[inline] + #[unstable(feature = "clamp_magnitude", issue = "148519")] + #[must_use = "this returns the clamped value and does not modify the original"] + pub fn clamp_magnitude(self, limit: f16) -> f16 { + assert!(limit >= 0.0, "limit must be non-negative"); + let limit = limit.abs(); // Canonicalises -0.0 to 0.0 + self.clamp(-limit, limit) + } + /// Computes the absolute value of `self`. /// /// This function always returns the precise result. diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index 3070e1dedbe43..91e276c5bc8e8 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -1431,6 +1431,35 @@ impl f32 { self } + /// Clamps this number to a symmetric range centered around zero. + /// + /// The method clamps the number's magnitude (absolute value) to be at most `limit`. + /// + /// This is functionally equivalent to `self.clamp(-limit, limit)`, but is more + /// explicit about the intent. + /// + /// # Panics + /// + /// Panics if `limit` is negative or NaN, as this indicates a logic error. + /// + /// # Examples + /// + /// ``` + /// #![feature(clamp_magnitude)] + /// assert_eq!(5.0f32.clamp_magnitude(3.0), 3.0); + /// assert_eq!((-5.0f32).clamp_magnitude(3.0), -3.0); + /// assert_eq!(2.0f32.clamp_magnitude(3.0), 2.0); + /// assert_eq!((-2.0f32).clamp_magnitude(3.0), -2.0); + /// ``` + #[must_use = "this returns the clamped value and does not modify the original"] + #[unstable(feature = "clamp_magnitude", issue = "148519")] + #[inline] + pub fn clamp_magnitude(self, limit: f32) -> f32 { + assert!(limit >= 0.0, "limit must be non-negative"); + let limit = limit.abs(); // Canonicalises -0.0 to 0.0 + self.clamp(-limit, limit) + } + /// Computes the absolute value of `self`. /// /// This function always returns the precise result. diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index dc8ccc551b2da..a4b2979c37d0d 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -1429,6 +1429,35 @@ impl f64 { self } + /// Clamps this number to a symmetric range centered around zero. + /// + /// The method clamps the number's magnitude (absolute value) to be at most `limit`. + /// + /// This is functionally equivalent to `self.clamp(-limit, limit)`, but is more + /// explicit about the intent. + /// + /// # Panics + /// + /// Panics if `limit` is negative or NaN, as this indicates a logic error. + /// + /// # Examples + /// + /// ``` + /// #![feature(clamp_magnitude)] + /// assert_eq!(5.0f64.clamp_magnitude(3.0), 3.0); + /// assert_eq!((-5.0f64).clamp_magnitude(3.0), -3.0); + /// assert_eq!(2.0f64.clamp_magnitude(3.0), 2.0); + /// assert_eq!((-2.0f64).clamp_magnitude(3.0), -2.0); + /// ``` + #[must_use = "this returns the clamped value and does not modify the original"] + #[unstable(feature = "clamp_magnitude", issue = "148519")] + #[inline] + pub fn clamp_magnitude(self, limit: f64) -> f64 { + assert!(limit >= 0.0, "limit must be non-negative"); + let limit = limit.abs(); // Canonicalises -0.0 to 0.0 + self.clamp(-limit, limit) + } + /// Computes the absolute value of `self`. /// /// This function always returns the precise result. diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index 7d395eb780346..9134d37636f0f 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -3855,5 +3855,32 @@ macro_rules! int_impl { pub const fn max_value() -> Self { Self::MAX } + + /// Clamps this number to a symmetric range centred around zero. + /// + /// The method clamps the number's magnitude (absolute value) to be at most `limit`. + /// + /// This is functionally equivalent to `self.clamp(-limit, limit)`, but is more + /// explicit about the intent. + /// + /// # Examples + /// + /// ``` + /// #![feature(clamp_magnitude)] + #[doc = concat!("assert_eq!(120", stringify!($SelfT), ".clamp_magnitude(100), 100);")] + #[doc = concat!("assert_eq!(-120", stringify!($SelfT), ".clamp_magnitude(100), -100);")] + #[doc = concat!("assert_eq!(80", stringify!($SelfT), ".clamp_magnitude(100), 80);")] + #[doc = concat!("assert_eq!(-80", stringify!($SelfT), ".clamp_magnitude(100), -80);")] + /// ``` + #[must_use = "this returns the clamped value and does not modify the original"] + #[unstable(feature = "clamp_magnitude", issue = "148519")] + #[inline] + pub fn clamp_magnitude(self, limit: $UnsignedT) -> Self { + if let Ok(limit) = core::convert::TryInto::<$SelfT>::try_into(limit) { + self.clamp(-limit, limit) + } else { + self + } + } } } diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 80b62038c40ec..124a8cf69385d 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -15,6 +15,7 @@ #![feature(cfg_target_has_reliable_f16_f128)] #![feature(char_internals)] #![feature(char_max_len)] +#![feature(clamp_magnitude)] #![feature(clone_to_uninit)] #![feature(const_cell_traits)] #![feature(const_cmp)] diff --git a/library/coretests/tests/num/clamp_magnitude.rs b/library/coretests/tests/num/clamp_magnitude.rs new file mode 100644 index 0000000000000..0f96e55f6914e --- /dev/null +++ b/library/coretests/tests/num/clamp_magnitude.rs @@ -0,0 +1,139 @@ +macro_rules! check_int_clamp { + ($t:ty, $ut:ty) => { + let min = <$t>::MIN; + let max = <$t>::MAX; + let max_u = <$ut>::MAX; + + // Basic clamping + assert_eq!((100 as $t).clamp_magnitude(50), 50); + assert_eq!((-100 as $t).clamp_magnitude(50), -50); + assert_eq!((30 as $t).clamp_magnitude(50), 30); + assert_eq!((-30 as $t).clamp_magnitude(50), -30); + + // Exact boundary + assert_eq!((50 as $t).clamp_magnitude(50), 50); + assert_eq!((-50 as $t).clamp_magnitude(50), -50); + + // Zero cases + assert_eq!((0 as $t).clamp_magnitude(100), 0); + assert_eq!((0 as $t).clamp_magnitude(0), 0); + assert_eq!((100 as $t).clamp_magnitude(0), 0); + assert_eq!((-100 as $t).clamp_magnitude(0), 0); + + // MIN/MAX values + // Symmetric range [-MAX, MAX] + assert_eq!(max.clamp_magnitude(max as $ut), max); + assert_eq!(min.clamp_magnitude(max as $ut), -max); + + // Full range (limit covers MIN) + let min_abs = min.unsigned_abs(); + assert_eq!(min.clamp_magnitude(min_abs), min); + + // Limit larger than type max (uN > iN::MAX) + assert_eq!(max.clamp_magnitude(max_u), max); + assert_eq!(min.clamp_magnitude(max_u), min); + }; +} + +#[test] +fn test_clamp_magnitude_i8() { + check_int_clamp!(i8, u8); +} + +#[test] +fn test_clamp_magnitude_i16() { + check_int_clamp!(i16, u16); +} + +#[test] +fn test_clamp_magnitude_i32() { + check_int_clamp!(i32, u32); +} + +#[test] +fn test_clamp_magnitude_i64() { + check_int_clamp!(i64, u64); +} + +#[test] +fn test_clamp_magnitude_i128() { + check_int_clamp!(i128, u128); +} + +#[test] +fn test_clamp_magnitude_isize() { + check_int_clamp!(isize, usize); +} + +macro_rules! check_float_clamp { + ($t:ty) => { + // Basic clamping + assert_eq!((5.0 as $t).clamp_magnitude(3.0), 3.0); + assert_eq!((-5.0 as $t).clamp_magnitude(3.0), -3.0); + assert_eq!((2.0 as $t).clamp_magnitude(3.0), 2.0); + assert_eq!((-2.0 as $t).clamp_magnitude(3.0), -2.0); + + // Exact boundary + assert_eq!((3.0 as $t).clamp_magnitude(3.0), 3.0); + assert_eq!((-3.0 as $t).clamp_magnitude(3.0), -3.0); + + // Zero cases + assert_eq!((0.0 as $t).clamp_magnitude(1.0), 0.0); + assert_eq!((-0.0 as $t).clamp_magnitude(1.0), 0.0); + assert_eq!((5.0 as $t).clamp_magnitude(0.0), 0.0); + assert_eq!((-5.0 as $t).clamp_magnitude(0.0), 0.0); + + // Special values - Infinity + let inf = <$t>::INFINITY; + let neg_inf = <$t>::NEG_INFINITY; + assert_eq!(inf.clamp_magnitude(100.0), 100.0); + assert_eq!(neg_inf.clamp_magnitude(100.0), -100.0); + assert_eq!(inf.clamp_magnitude(inf), inf); + + // Value with infinite limit + assert_eq!((1.0 as $t).clamp_magnitude(inf), 1.0); + assert_eq!((-1.0 as $t).clamp_magnitude(inf), -1.0); + + // MIN and MAX + let max = <$t>::MAX; + let min = <$t>::MIN; + // Large limit + let huge = 1e30; + assert_eq!(max.clamp_magnitude(huge), huge); + assert_eq!(min.clamp_magnitude(huge), -huge); + }; +} + +#[test] +fn test_clamp_magnitude_f32() { + check_float_clamp!(f32); +} + +#[test] +fn test_clamp_magnitude_f64() { + check_float_clamp!(f64); +} + +#[test] +#[should_panic(expected = "limit must be non-negative")] +fn test_clamp_magnitude_f32_panic_negative_limit() { + let _ = 1.0f32.clamp_magnitude(-1.0); +} + +#[test] +#[should_panic(expected = "limit must be non-negative")] +fn test_clamp_magnitude_f64_panic_negative_limit() { + let _ = 1.0f64.clamp_magnitude(-1.0); +} + +#[test] +#[should_panic] +fn test_clamp_magnitude_f32_panic_nan_limit() { + let _ = 1.0f32.clamp_magnitude(f32::NAN); +} + +#[test] +#[should_panic] +fn test_clamp_magnitude_f64_panic_nan_limit() { + let _ = 1.0f64.clamp_magnitude(f64::NAN); +}