From e9ed7652725727480b0ba4050811dba174dc5cda Mon Sep 17 00:00:00 2001 From: Kyuuhachi Date: Tue, 16 Dec 2025 23:03:24 +0100 Subject: [PATCH 1/3] Implement clamp_to --- library/core/src/cmp.rs | 32 ++++++++++++ library/core/src/cmp/clamp.rs | 98 +++++++++++++++++++++++++++++++++++ library/core/src/num/f128.rs | 33 ++++++++++++ library/core/src/num/f16.rs | 33 ++++++++++++ library/core/src/num/f32.rs | 33 ++++++++++++ library/core/src/num/f64.rs | 33 ++++++++++++ 6 files changed, 262 insertions(+) create mode 100644 library/core/src/cmp/clamp.rs diff --git a/library/core/src/cmp.rs b/library/core/src/cmp.rs index feb9c43196044..4dd5bc2e3c990 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}; @@ -1096,6 +1099,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..dd496babbc659 --- /dev/null +++ b/library/core/src/cmp/clamp.rs @@ -0,0 +1,98 @@ +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 { + 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 { + 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(); + // Deliberately avoid using `clamp` to handle NaN consistently + value.max(start).min(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 bf99fee4fc78a..fb2659d4ca86f 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -1325,6 +1325,39 @@ impl f128 { self.clamp(-limit, limit) } + /// Restrict a value to a certain range. + /// + /// This is largely equal to `max`, `min`, or `clamp`, depending on whether the range is + /// `min..`, `..=max`, or `min..=max`, respectively. However, whereas `clamp` panics on NaN + /// values, this function treats them as unbounded, like `max` and `min`. + /// + /// Exclusive ranges are not permitted. + /// + /// # Panics + /// + /// Panics on `min..=max` if `min > max`. + /// + /// # 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_eq!(4.0f128.clamp_to(1.0..=f128::NAN), 4.0); + /// ``` + #[must_use] + #[inline] + #[unstable(feature = "clamp_to", issue = "147781")] + pub fn clamp_to(self, range: R) -> Self + where + Self: Sized, + 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 f39ee22871d5f..686537f7cbfb5 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -1303,6 +1303,39 @@ impl f16 { self.clamp(-limit, limit) } + /// Restrict a value to a certain range. + /// + /// This is largely equal to `max`, `min`, or `clamp`, depending on whether the range is + /// `min..`, `..=max`, or `min..=max`, respectively. However, whereas `clamp` panics on NaN + /// values, this function treats them as unbounded, like `max` and `min`. + /// + /// Exclusive ranges are not permitted. + /// + /// # Panics + /// + /// Panics on `min..=max` if `min > max`. + /// + /// # 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_eq!(4.0f16.clamp_to(1.0..=f16::NAN), 4.0); + /// ``` + #[must_use] + #[inline] + #[unstable(feature = "clamp_to", issue = "147781")] + pub fn clamp_to(self, range: R) -> Self + where + Self: Sized, + 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 6fe4285374b23..daad80085f913 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -1477,6 +1477,39 @@ impl f32 { self.clamp(-limit, limit) } + /// Restrict a value to a certain range. + /// + /// This is largely equal to `max`, `min`, or `clamp`, depending on whether the range is + /// `min..`, `..=max`, or `min..=max`, respectively. However, whereas `clamp` panics on NaN + /// values, this function treats them as unbounded, like `max` and `min`. + /// + /// Exclusive ranges are not permitted. + /// + /// # Panics + /// + /// Panics on `min..=max` if `min > max`. + /// + /// # 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_eq!(4.0f32.clamp_to(1.0..=f32::NAN), 4.0); + /// ``` + #[must_use] + #[inline] + #[unstable(feature = "clamp_to", issue = "147781")] + pub fn clamp_to(self, range: R) -> Self + where + Self: Sized, + 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 d0aca152415e2..dce8d761644bd 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -1475,6 +1475,39 @@ impl f64 { self.clamp(-limit, limit) } + /// Restrict a value to a certain range. + /// + /// This is largely equal to `max`, `min`, or `clamp`, depending on whether the range is + /// `min..`, `..=max`, or `min..=max`, respectively. However, whereas `clamp` panics on NaN + /// values, this function treats them as unbounded, like `max` and `min`. + /// + /// Exclusive ranges are not permitted. + /// + /// # Panics + /// + /// Panics on `min..=max` if `min > max`. + /// + /// # 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_eq!(4.0f64.clamp_to(1.0..=f64::NAN), 4.0); + /// ``` + #[must_use] + #[inline] + #[unstable(feature = "clamp_to", issue = "147781")] + pub fn clamp_to(self, range: R) -> Self + where + Self: Sized, + R: crate::cmp::ClampBounds, + { + range.clamp(self) + } + /// Computes the absolute value of `self`. /// /// This function always returns the precise result. From f6e951ea576011a7c839f38e211dfe310355d1e8 Mon Sep 17 00:00:00 2001 From: Kyuuhachi Date: Thu, 18 Dec 2025 19:01:31 +0100 Subject: [PATCH 2/3] Remove trivial bounds --- library/core/src/num/f128.rs | 1 - library/core/src/num/f16.rs | 1 - library/core/src/num/f32.rs | 1 - library/core/src/num/f64.rs | 1 - 4 files changed, 4 deletions(-) diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs index fb2659d4ca86f..7d2ff4c622187 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -1352,7 +1352,6 @@ impl f128 { #[unstable(feature = "clamp_to", issue = "147781")] pub fn clamp_to(self, range: R) -> Self where - Self: Sized, R: crate::cmp::ClampBounds, { range.clamp(self) diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs index 686537f7cbfb5..1cd02338609a1 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -1330,7 +1330,6 @@ impl f16 { #[unstable(feature = "clamp_to", issue = "147781")] pub fn clamp_to(self, range: R) -> Self where - Self: Sized, R: crate::cmp::ClampBounds, { range.clamp(self) diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index daad80085f913..08c9a7943259b 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -1504,7 +1504,6 @@ impl f32 { #[unstable(feature = "clamp_to", issue = "147781")] pub fn clamp_to(self, range: R) -> Self where - Self: Sized, R: crate::cmp::ClampBounds, { range.clamp(self) diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index dce8d761644bd..4a523eef33fd7 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -1502,7 +1502,6 @@ impl f64 { #[unstable(feature = "clamp_to", issue = "147781")] pub fn clamp_to(self, range: R) -> Self where - Self: Sized, R: crate::cmp::ClampBounds, { range.clamp(self) From aeb4276915405f04c442de807dda8f7412a6a24f Mon Sep 17 00:00:00 2001 From: Kyuuhachi Date: Thu, 18 Dec 2025 23:30:15 +0100 Subject: [PATCH 3/3] Panic on NaN --- library/core/src/cmp/clamp.rs | 5 +++-- library/core/src/num/f128.rs | 13 ++++++++----- library/core/src/num/f16.rs | 13 ++++++++----- library/core/src/num/f32.rs | 13 ++++++++----- library/core/src/num/f64.rs | 13 ++++++++----- 5 files changed, 35 insertions(+), 22 deletions(-) diff --git a/library/core/src/cmp/clamp.rs b/library/core/src/cmp/clamp.rs index dd496babbc659..e2dcecc533577 100644 --- a/library/core/src/cmp/clamp.rs +++ b/library/core/src/cmp/clamp.rs @@ -67,6 +67,7 @@ macro impl_for_float($t:ty) { #[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(), "min was NaN"); value.max(self.start) } } @@ -75,6 +76,7 @@ macro impl_for_float($t:ty) { #[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(), "max was NaN"); value.min(self.end) } } @@ -84,8 +86,7 @@ macro impl_for_float($t:ty) { impl const ClampBounds<$t> for RangeInclusive<$t> { fn clamp(self, value: $t) -> $t { let (start, end) = self.into_inner(); - // Deliberately avoid using `clamp` to handle NaN consistently - value.max(start).min(end) + value.clamp(start, end) } } } diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs index 7d2ff4c622187..18dcd466f926b 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -1325,17 +1325,20 @@ impl f128 { self.clamp(-limit, limit) } - /// Restrict a value to a certain range. + /// 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, whereas `clamp` panics on NaN - /// values, this function treats them as unbounded, like `max` and `min`. + /// `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`. + /// Panics on `min..=max` if `min > max`, or if any bound is NaN. /// /// # Examples /// @@ -1345,7 +1348,7 @@ impl f128 { /// 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_eq!(4.0f128.clamp_to(1.0..=f128::NAN), 4.0); + /// assert!(f128::NAN.clamp_to(1.0..=2.0).is_nan()); /// ``` #[must_use] #[inline] diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs index 1cd02338609a1..70a539caaa71d 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -1303,17 +1303,20 @@ impl f16 { self.clamp(-limit, limit) } - /// Restrict a value to a certain range. + /// 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, whereas `clamp` panics on NaN - /// values, this function treats them as unbounded, like `max` and `min`. + /// `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`. + /// Panics on `min..=max` if `min > max`, or if any bound is NaN. /// /// # Examples /// @@ -1323,7 +1326,7 @@ impl f16 { /// 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_eq!(4.0f16.clamp_to(1.0..=f16::NAN), 4.0); + /// assert!(f16::NAN.clamp_to(1.0..=2.0).is_nan()); /// ``` #[must_use] #[inline] diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index 08c9a7943259b..e1f213eb7b3fa 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -1477,17 +1477,20 @@ impl f32 { self.clamp(-limit, limit) } - /// Restrict a value to a certain range. + /// 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, whereas `clamp` panics on NaN - /// values, this function treats them as unbounded, like `max` and `min`. + /// `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`. + /// Panics on `min..=max` if `min > max`, or if any bound is NaN. /// /// # Examples /// @@ -1497,7 +1500,7 @@ impl f32 { /// 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_eq!(4.0f32.clamp_to(1.0..=f32::NAN), 4.0); + /// assert!(f32::NAN.clamp_to(1.0..=2.0).is_nan()); /// ``` #[must_use] #[inline] diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index 4a523eef33fd7..ccb17dc5913ba 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -1475,17 +1475,20 @@ impl f64 { self.clamp(-limit, limit) } - /// Restrict a value to a certain range. + /// 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, whereas `clamp` panics on NaN - /// values, this function treats them as unbounded, like `max` and `min`. + /// `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`. + /// Panics on `min..=max` if `min > max`, or if any bound is NaN. /// /// # Examples /// @@ -1495,7 +1498,7 @@ impl f64 { /// 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_eq!(4.0f64.clamp_to(1.0..=f64::NAN), 4.0); + /// assert!(f64::NAN.clamp_to(1.0..=2.0).is_nan()); /// ``` #[must_use] #[inline]