diff --git a/library/core/src/cmp.rs b/library/core/src/cmp.rs index 87f0638df183e..8db7c72a837a9 100644 --- a/library/core/src/cmp.rs +++ b/library/core/src/cmp.rs @@ -26,7 +26,10 @@ #![stable(feature = "rust1", since = "1.0.0")] mod bytewise; +mod clamp; pub(crate) use bytewise::BytewiseEq; +#[unstable(feature = "clamp_bounds", issue = "147781")] +pub use clamp::ClampBounds; use self::Ordering::*; use crate::marker::{Destruct, PointeeSized}; @@ -1169,6 +1172,35 @@ pub const trait Ord: [const] Eq + [const] PartialOrd + PointeeSized { self } } + + /// Restrict a value to a certain range. + /// + /// This is equal to `max`, `min`, or `clamp`, depending on whether the range is `min..`, + /// `..=max`, or `min..=max`, respectively. Exclusive ranges are not permitted. + /// + /// # Panics + /// + /// Panics on `min..=max` if `min > max`. + /// + /// # Examples + /// + /// ``` + /// #![feature(clamp_to)] + /// assert_eq!((-3).clamp_to(-2..=1), -2); + /// assert_eq!(0.clamp_to(-2..=1), 0); + /// assert_eq!(2.clamp_to(..=1), 1); + /// assert_eq!(5.clamp_to(7..), 7); + /// ``` + #[must_use] + #[inline] + #[unstable(feature = "clamp_to", issue = "147781")] + fn clamp_to(self, range: R) -> Self + where + Self: Sized + [const] Destruct, + R: [const] ClampBounds, + { + range.clamp(self) + } } /// Derive macro generating an impl of the trait [`Ord`]. diff --git a/library/core/src/cmp/clamp.rs b/library/core/src/cmp/clamp.rs new file mode 100644 index 0000000000000..a737b59bbbe51 --- /dev/null +++ b/library/core/src/cmp/clamp.rs @@ -0,0 +1,104 @@ +use crate::marker::Destruct; +use crate::ops::{RangeFrom, RangeFull, RangeInclusive, RangeToInclusive}; + +/// Trait for ranges supported by [`Ord::clamp_to`]. +#[unstable(feature = "clamp_bounds", issue = "147781")] +#[rustc_const_unstable(feature = "clamp_bounds", issue = "147781")] +pub const trait ClampBounds: Sized { + /// The implementation of [`Ord::clamp_to`]. + fn clamp(self, value: T) -> T + where + T: [const] Destruct; +} + +#[unstable(feature = "clamp_bounds", issue = "147781")] +#[rustc_const_unstable(feature = "clamp_bounds", issue = "147781")] +const impl ClampBounds for RangeFrom +where + T: [const] Ord, +{ + fn clamp(self, value: T) -> T + where + T: [const] Destruct, + { + value.max(self.start) + } +} + +#[unstable(feature = "clamp_bounds", issue = "147781")] +#[rustc_const_unstable(feature = "clamp_bounds", issue = "147781")] +const impl ClampBounds for RangeToInclusive +where + T: [const] Ord, +{ + fn clamp(self, value: T) -> T + where + T: [const] Destruct, + { + value.min(self.end) + } +} + +#[unstable(feature = "clamp_bounds", issue = "147781")] +#[rustc_const_unstable(feature = "clamp_bounds", issue = "147781")] +const impl ClampBounds for RangeInclusive +where + T: [const] Ord, +{ + fn clamp(self, value: T) -> T + where + T: [const] Destruct, + { + let (start, end) = self.into_inner(); + value.clamp(start, end) + } +} + +#[unstable(feature = "clamp_bounds", issue = "147781")] +#[rustc_const_unstable(feature = "clamp_bounds", issue = "147781")] +const impl ClampBounds for RangeFull { + fn clamp(self, value: T) -> T { + value + } +} + +macro impl_for_float($t:ty) { + #[unstable(feature = "clamp_bounds", issue = "147781")] + #[rustc_const_unstable(feature = "clamp_bounds", issue = "147781")] + const impl ClampBounds<$t> for RangeFrom<$t> { + fn clamp(self, value: $t) -> $t { + assert!(!self.start.is_nan(), "start was NaN"); + value.max(self.start) + } + } + + #[unstable(feature = "clamp_bounds", issue = "147781")] + #[rustc_const_unstable(feature = "clamp_bounds", issue = "147781")] + const impl ClampBounds<$t> for RangeToInclusive<$t> { + fn clamp(self, value: $t) -> $t { + assert!(!self.end.is_nan(), "end was NaN"); + value.min(self.end) + } + } + + #[unstable(feature = "clamp_bounds", issue = "147781")] + #[rustc_const_unstable(feature = "clamp_bounds", issue = "147781")] + const impl ClampBounds<$t> for RangeInclusive<$t> { + #[expect( + clippy::neg_cmp_op_on_partial_ord, + reason = "NaN check is intentionally included in comparison" + )] + fn clamp(self, value: $t) -> $t { + let (start, end) = self.into_inner(); + assert!(start <= end, "start > end, or either was NaN"); + value.clamp(start, end) + } + } +} + +// #[unstable(feature = "f16", issue = "116909")] +impl_for_float!(f16); +impl_for_float!(f32); +impl_for_float!(f64); +// #[unstable(feature = "f128", issue = "116909")] +impl_for_float!(f128); diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs index d52e817c9e3db..93dd52198f488 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -1483,7 +1483,7 @@ impl f128 { /// /// ``` /// #![feature(f128)] - /// # #[cfg(target_has_reliable_f128)] { + /// # #[cfg(target_has_reliable_f128_math)] { /// /// assert!((-3.0f128).clamp(-2.0, 1.0) == -2.0); /// assert!((0.0f128).clamp(-2.0, 1.0) == 0.0); @@ -1552,6 +1552,43 @@ impl f128 { self.clamp(-limit, limit) } + /// Restrict a value to a certain range, unless it is NaN. + /// + /// This is largely equal to `max`, `min`, or `clamp`, depending on whether the range is + /// `min..`, `..=max`, or `min..=max`, respectively. However, unlike `max` and `min`, it will + /// panic if any bound is NaN. + /// + /// Note that this function returns NaN if the initial value was NaN as + /// well. + /// + /// Exclusive ranges are not permitted. + /// + /// # Panics + /// + /// Panics on `min..=max` if `min > max`, or if any bound is NaN. + /// + /// # Examples + /// + /// ``` + /// #![feature(f128, clamp_to)] + /// # #[cfg(target_has_reliable_f128_math)] { + /// assert_eq!((-3.0f128).clamp_to(-2.0..=1.0), -2.0); + /// assert_eq!(0.0f128.clamp_to(-2.0..=1.0), 0.0); + /// assert_eq!(2.0f128.clamp_to(..=1.0), 1.0); + /// assert_eq!(5.0f128.clamp_to(7.0..), 7.0); + /// assert!(f128::NAN.clamp_to(1.0..=2.0).is_nan()); + /// # } + /// ``` + #[must_use] + #[inline] + #[unstable(feature = "clamp_to", issue = "147781")] + pub fn clamp_to(self, range: R) -> Self + where + R: crate::cmp::ClampBounds, + { + range.clamp(self) + } + /// 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 186e83a9cd6b5..cb79c0736c608 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -1469,7 +1469,7 @@ impl f16 { /// /// ``` /// #![feature(f16)] - /// # #[cfg(target_has_reliable_f16)] { + /// # #[cfg(target_has_reliable_f16_math)] { /// /// assert!((-3.0f16).clamp(-2.0, 1.0) == -2.0); /// assert!((0.0f16).clamp(-2.0, 1.0) == 0.0); @@ -1538,6 +1538,43 @@ impl f16 { self.clamp(-limit, limit) } + /// Restrict a value to a certain range, unless it is NaN. + /// + /// This is largely equal to `max`, `min`, or `clamp`, depending on whether the range is + /// `min..`, `..=max`, or `min..=max`, respectively. However, unlike `max` and `min`, it will + /// panic if any bound is NaN. + /// + /// Note that this function returns NaN if the initial value was NaN as + /// well. + /// + /// Exclusive ranges are not permitted. + /// + /// # Panics + /// + /// Panics on `min..=max` if `min > max`, or if any bound is NaN. + /// + /// # Examples + /// + /// ``` + /// #![feature(f16, clamp_to)] + /// # #[cfg(target_has_reliable_f16_math)] { + /// assert_eq!((-3.0f16).clamp_to(-2.0..=1.0), -2.0); + /// assert_eq!(0.0f16.clamp_to(-2.0..=1.0), 0.0); + /// assert_eq!(2.0f16.clamp_to(..=1.0), 1.0); + /// assert_eq!(5.0f16.clamp_to(7.0..), 7.0); + /// assert!(f16::NAN.clamp_to(1.0..=2.0).is_nan()); + /// # } + /// ``` + #[must_use] + #[inline] + #[unstable(feature = "clamp_to", issue = "147781")] + pub fn clamp_to(self, range: R) -> Self + where + R: crate::cmp::ClampBounds, + { + range.clamp(self) + } + /// 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 3c6b58a2b4b25..8a02aa7517474 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -1709,6 +1709,41 @@ impl f32 { self.clamp(-limit, limit) } + /// Restrict a value to a certain range, unless it is NaN. + /// + /// This is largely equal to `max`, `min`, or `clamp`, depending on whether the range is + /// `min..`, `..=max`, or `min..=max`, respectively. However, unlike `max` and `min`, it will + /// panic if any bound is NaN. + /// + /// Note that this function returns NaN if the initial value was NaN as + /// well. + /// + /// Exclusive ranges are not permitted. + /// + /// # Panics + /// + /// Panics on `min..=max` if `min > max`, or if any bound is NaN. + /// + /// # Examples + /// + /// ``` + /// #![feature(clamp_to)] + /// assert_eq!((-3.0f32).clamp_to(-2.0..=1.0), -2.0); + /// assert_eq!(0.0f32.clamp_to(-2.0..=1.0), 0.0); + /// assert_eq!(2.0f32.clamp_to(..=1.0), 1.0); + /// assert_eq!(5.0f32.clamp_to(7.0..), 7.0); + /// assert!(f32::NAN.clamp_to(1.0..=2.0).is_nan()); + /// ``` + #[must_use] + #[inline] + #[unstable(feature = "clamp_to", issue = "147781")] + pub fn clamp_to(self, range: R) -> Self + where + R: crate::cmp::ClampBounds, + { + range.clamp(self) + } + /// 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 8c433a5cf941d..e0bb0e35415b6 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -1687,6 +1687,41 @@ impl f64 { self.clamp(-limit, limit) } + /// Restrict a value to a certain range, unless it is NaN. + /// + /// This is largely equal to `max`, `min`, or `clamp`, depending on whether the range is + /// `min..`, `..=max`, or `min..=max`, respectively. However, unlike `max` and `min`, it will + /// panic if any bound is NaN. + /// + /// Note that this function returns NaN if the initial value was NaN as + /// well. + /// + /// Exclusive ranges are not permitted. + /// + /// # Panics + /// + /// Panics on `min..=max` if `min > max`, or if any bound is NaN. + /// + /// # Examples + /// + /// ``` + /// #![feature(clamp_to)] + /// assert_eq!((-3.0f64).clamp_to(-2.0..=1.0), -2.0); + /// assert_eq!(0.0f64.clamp_to(-2.0..=1.0), 0.0); + /// assert_eq!(2.0f64.clamp_to(..=1.0), 1.0); + /// assert_eq!(5.0f64.clamp_to(7.0..), 7.0); + /// assert!(f64::NAN.clamp_to(1.0..=2.0).is_nan()); + /// ``` + #[must_use] + #[inline] + #[unstable(feature = "clamp_to", issue = "147781")] + pub fn clamp_to(self, range: R) -> Self + where + R: crate::cmp::ClampBounds, + { + range.clamp(self) + } + /// Computes the absolute value of `self`. /// /// This function always returns the precise result. diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index c993c947929cd..34a4fc74055d0 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -14,6 +14,7 @@ #![feature(cfg_overflow_checks)] #![feature(cfg_target_has_reliable_f16_f128)] #![feature(char_internals)] +#![feature(clamp_to)] #![feature(clone_to_uninit)] #![feature(cmp_minmax)] #![feature(cmp_splat)] diff --git a/library/coretests/tests/num/floats.rs b/library/coretests/tests/num/floats.rs index 8c3bc2f61b4fe..b8f75489bb212 100644 --- a/library/coretests/tests/num/floats.rs +++ b/library/coretests/tests/num/floats.rs @@ -1328,10 +1328,10 @@ float_test! { name: clamp_min_greater_than_max, attrs: { const: #[cfg(false)], - f16: #[should_panic, cfg(target_has_reliable_f16)], + f16: #[should_panic, cfg(target_has_reliable_f16_math)], f32: #[should_panic], f64: #[should_panic], - f128: #[should_panic, cfg(target_has_reliable_f128)], + f128: #[should_panic, cfg(target_has_reliable_f128_math)], }, test { let _ = Float::ONE.clamp(3.0, 1.0); @@ -1342,10 +1342,10 @@ float_test! { name: clamp_min_is_nan, attrs: { const: #[cfg(false)], - f16: #[should_panic, cfg(target_has_reliable_f16)], + f16: #[should_panic, cfg(target_has_reliable_f16_math)], f32: #[should_panic], f64: #[should_panic], - f128: #[should_panic, cfg(target_has_reliable_f128)], + f128: #[should_panic, cfg(target_has_reliable_f128_math)], }, test { let _ = Float::ONE.clamp(Float::NAN, 1.0); @@ -1356,16 +1356,58 @@ float_test! { name: clamp_max_is_nan, attrs: { const: #[cfg(false)], - f16: #[should_panic, cfg(target_has_reliable_f16)], + f16: #[should_panic, cfg(target_has_reliable_f16_math)], f32: #[should_panic], f64: #[should_panic], - f128: #[should_panic, cfg(target_has_reliable_f128)], + f128: #[should_panic, cfg(target_has_reliable_f128_math)], }, test { let _ = Float::ONE.clamp(3.0, Float::NAN); } } +float_test! { + name: clamp_to_min_greater_than_max, + attrs: { + const: #[cfg(false)], + f16: #[should_panic, cfg(target_has_reliable_f16_math)], + f32: #[should_panic], + f64: #[should_panic], + f128: #[should_panic, cfg(target_has_reliable_f128_math)], + }, + test { + let _ = Float::ONE.clamp_to(3.0..=1.0); + } +} + +float_test! { + name: clamp_to_min_is_nan, + attrs: { + const: #[cfg(false)], + f16: #[should_panic, cfg(target_has_reliable_f16_math)], + f32: #[should_panic], + f64: #[should_panic], + f128: #[should_panic, cfg(target_has_reliable_f128_math)], + }, + test { + let _ = Float::ONE.clamp_to(Float::NAN..=1.0); + } +} + +float_test! { + name: clamp_to_max_is_nan, + attrs: { + const: #[cfg(false)], + f16: #[should_panic, cfg(target_has_reliable_f16_math)], + f32: #[should_panic], + f64: #[should_panic], + f128: #[should_panic, cfg(target_has_reliable_f128_math)], + }, + test { + let _ = Float::ONE.clamp_to(3.0..=Float::NAN); + } +} + float_test! { name: total_cmp, attrs: {