diff --git a/library/core/src/cmp.rs b/library/core/src/cmp.rs index 2051a806af642..c147571a361a5 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}; @@ -1108,6 +1111,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..d6a67c22e6cee --- /dev/null +++ b/library/core/src/cmp/clamp.rs @@ -0,0 +1,100 @@ +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")] +impl const 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")] +impl const 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")] +impl const 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")] +impl const 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")] + impl const 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")] + impl const 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")] + impl const ClampBounds<$t> for RangeInclusive<$t> { + 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 c17f55a25896a..8f70f94194e7c 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -1454,6 +1454,41 @@ 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)] + /// 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 110465068b8f6..4dc8072d956d0 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -1440,6 +1440,41 @@ 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)] + /// 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 f9cb7cc650f4f..53cc5d27bcaa4 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -1608,6 +1608,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 87f5505ce2b33..4a2b7cc6184e8 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -1588,6 +1588,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 12b81fea9d27c..992b7697f2b49 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -13,6 +13,7 @@ #![feature(bstr)] #![feature(cfg_target_has_reliable_f16_f128)] #![feature(char_internals)] +#![feature(clamp_to)] #![feature(clone_to_uninit)] #![feature(cmp_minmax)] #![feature(const_array)] diff --git a/library/coretests/tests/num/floats.rs b/library/coretests/tests/num/floats.rs index 1d7956b41c9d1..e20540091da0c 100644 --- a/library/coretests/tests/num/floats.rs +++ b/library/coretests/tests/num/floats.rs @@ -1359,6 +1359,48 @@ float_test! { } } +float_test! { + name: clamp_to_min_greater_than_max, + attrs: { + const: #[cfg(false)], + f16: #[should_panic, cfg(target_has_reliable_f16)], + f32: #[should_panic], + f64: #[should_panic], + f128: #[should_panic, cfg(target_has_reliable_f128)], + }, + 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)], + f32: #[should_panic], + f64: #[should_panic], + f128: #[should_panic, cfg(target_has_reliable_f128)], + }, + 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)], + f32: #[should_panic], + f64: #[should_panic], + f128: #[should_panic, cfg(target_has_reliable_f128)], + }, + test { + let _ = Float::ONE.clamp_to(3.0..=Float::NAN); + } +} + float_test! { name: total_cmp, attrs: {