Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions library/core/src/num/f128.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1544,9 +1544,10 @@ impl f128 {
/// ```
#[inline]
#[unstable(feature = "clamp_magnitude", issue = "148519")]
#[must_use = "this returns the clamped value and does not modify the original"]
#[rustc_const_unstable(feature = "clamp_magnitude", issue = "148519")]
#[must_use = "method returns a new number and does not mutate the original value"]
#[expect(clippy::neg_cmp_op_on_partial_ord, reason = "NaN is also invalid")]
pub fn clamp_magnitude(self, limit: f128) -> f128 {
pub const fn clamp_magnitude(self, limit: f128) -> f128 {
assert!(limit >= 0.0, "limit must be non-negative and not NaN");
let limit = limit.abs(); // Canonicalises -0.0 to 0.0
self.clamp(-limit, limit)
Expand Down
5 changes: 3 additions & 2 deletions library/core/src/num/f16.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1530,9 +1530,10 @@ impl f16 {
/// ```
#[inline]
#[unstable(feature = "clamp_magnitude", issue = "148519")]
#[must_use = "this returns the clamped value and does not modify the original"]
#[rustc_const_unstable(feature = "clamp_magnitude", issue = "148519")]
#[must_use = "method returns a new number and does not mutate the original value"]
#[expect(clippy::neg_cmp_op_on_partial_ord, reason = "NaN is also invalid")]
pub fn clamp_magnitude(self, limit: f16) -> f16 {
pub const fn clamp_magnitude(self, limit: f16) -> f16 {
assert!(limit >= 0.0, "limit must be non-negative and not NaN");
let limit = limit.abs(); // Canonicalises -0.0 to 0.0
self.clamp(-limit, limit)
Expand Down
6 changes: 3 additions & 3 deletions library/core/src/num/f32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1699,11 +1699,11 @@ impl f32 {
/// 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]
#[unstable(feature = "clamp_magnitude", issue = "148519")]
#[must_use = "method returns a new number and does not mutate the original value"]
#[expect(clippy::neg_cmp_op_on_partial_ord, reason = "NaN is also invalid")]
pub fn clamp_magnitude(self, limit: f32) -> f32 {
pub const fn clamp_magnitude(self, limit: f32) -> f32 {
assert!(limit >= 0.0, "limit must be non-negative and not NaN");
let limit = limit.abs(); // Canonicalises -0.0 to 0.0
self.clamp(-limit, limit)
Expand Down
6 changes: 3 additions & 3 deletions library/core/src/num/f64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1677,11 +1677,11 @@ impl f64 {
/// 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]
#[unstable(feature = "clamp_magnitude", issue = "148519")]
#[must_use = "method returns a new number and does not mutate the original value"]
#[expect(clippy::neg_cmp_op_on_partial_ord, reason = "NaN is also invalid")]
pub fn clamp_magnitude(self, limit: f64) -> f64 {
pub const fn clamp_magnitude(self, limit: f64) -> f64 {
assert!(limit >= 0.0, "limit must be non-negative and not NaN");
let limit = limit.abs(); // Canonicalises -0.0 to 0.0
self.clamp(-limit, limit)
Expand Down
11 changes: 6 additions & 5 deletions library/core/src/num/int_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3964,14 +3964,15 @@ macro_rules! int_impl {
/// ```
/// #![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!((-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);")]
#[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 {
#[must_use = "method returns a new number and does not mutate the original value"]
#[rustc_const_unstable(feature = "const_cmp", issue = "143800")]
#[unstable(feature = "clamp_magnitude", issue = "148519")]
pub const fn clamp_magnitude(self, limit: $UnsignedT) -> Self {
if let Ok(limit) = core::convert::TryInto::<$SelfT>::try_into(limit) {

@Darksonn Darksonn Sep 25, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're making this const. Note that this implementation depends on traits, so stabilization as const may be an issue.

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. const impl Ord is gated behind #[rustc_const_unstable(feature = "const_cmp", issue = "143800")], so const fn clamp_magnitude should be too. I will address this.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gating it behind const_cmp seems wrong/weird to me ... most likely it should just be implemented without using the const trait, or it should wait for said const trait to stabilize.

@Eleocraft Eleocraft Sep 28, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clamp is part of the Ord trait. Generally const functions that depend on functions in the ord trait seem to have rustc_const_unstable gated behind "const_cmp". Look for example to the total_cmp() functions in the float types that are stable, but gated rustc_const_unstable behind "const_cmp" since they depend on cmp(). The only reason why clamp_magnitude() is not itself part of Ord is that it requires Neg (and therefore is only applicable to signed types). I also don't see how I would implement clamp_magnitude() without Ord, that seems like it would result in a lot of redundant code. If that does not convince you I am fine removing the const in int_macros.rs and nonzero.rs for now, however that partially defeats the purpose of this PR.

self.clamp(-limit, limit)
} else {
Expand Down
44 changes: 44 additions & 0 deletions library/core/src/num/nonzero.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2454,6 +2454,50 @@ macro_rules! nonzero_integer_signedness_dependent_methods {
unsafe { NonZero::new_unchecked(self.get().cast_unsigned()) }
}

/// 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)]
/// # use std::num::NonZero;
/// #
#[doc = concat!("let limit = NonZero::<", stringify!($Uint), ">::new(100).unwrap();")]
///
/// assert_eq!(
#[doc = concat!(" NonZero::<", stringify!($Int), ">::new(120).unwrap().clamp_magnitude(limit),")]
#[doc = concat!(" NonZero::<", stringify!($Int), ">::new(100).unwrap(),")]
/// );
///
/// assert_eq!(
#[doc = concat!(" NonZero::<", stringify!($Int), ">::new(-120).unwrap().clamp_magnitude(limit),")]
#[doc = concat!(" NonZero::<", stringify!($Int), ">::new(-100).unwrap(),")]
/// );
///
/// assert_eq!(
#[doc = concat!(" NonZero::<", stringify!($Int), ">::new(80).unwrap().clamp_magnitude(limit),")]
#[doc = concat!(" NonZero::<", stringify!($Int), ">::new(80).unwrap(),")]
/// );
///
/// assert_eq!(
#[doc = concat!(" NonZero::<", stringify!($Int), ">::new(-80).unwrap().clamp_magnitude(limit),")]
#[doc = concat!(" NonZero::<", stringify!($Int), ">::new(-80).unwrap(),")]
/// );
/// ```
#[inline]
#[must_use = "method returns a new number and does not mutate the original value"]
#[rustc_const_unstable(feature = "const_cmp", issue = "143800")]
#[unstable(feature = "clamp_magnitude", issue = "148519")]
pub const fn clamp_magnitude(self, limit: NonZero<$Uint>) -> Self {
Comment thread
Eleocraft marked this conversation as resolved.
Resolved
// SAFETY: a non-zero value clamped to the magnitude of a non-zero value is still non-zero.
unsafe { Self::new_unchecked(self.get().clamp_magnitude(limit.get())) }
}

};
}

Expand Down
1 change: 1 addition & 0 deletions library/coretests/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#![feature(cfg_overflow_checks)]
#![feature(cfg_target_has_reliable_f16_f128)]
#![feature(char_internals)]
#![feature(clamp_magnitude)]
#![feature(clamp_to)]
#![feature(clone_to_uninit)]
#![feature(cmp_minmax)]
Expand Down
171 changes: 157 additions & 14 deletions library/coretests/tests/num/clamp_magnitude.rs
Comment thread
Eleocraft marked this conversation as resolved.
Resolved
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use core::num::*;

use crate::num::assert_biteq;

macro_rules! check_int_clamp {
($t:ty, $ut:ty) => {
let min = <$t>::MIN;
Expand Down Expand Up @@ -32,6 +36,16 @@ macro_rules! check_int_clamp {
// Limit larger than type max (uN > iN::MAX)
assert_eq!(max.clamp_magnitude(max_u), max);
assert_eq!(min.clamp_magnitude(max_u), min);

// Const clamping
const C1: $t = (100 as $t).clamp_magnitude(50);
assert_eq!(C1, 50);
const C2: $t = (-100 as $t).clamp_magnitude(50);
assert_eq!(C2, -50);
const C3: $t = (30 as $t).clamp_magnitude(50);
assert_eq!(C3, 30);
const C4: $t = (-30 as $t).clamp_magnitude(50);
assert_eq!(C4, -30);
};
}

Expand Down Expand Up @@ -65,8 +79,81 @@ fn test_clamp_magnitude_isize() {
check_int_clamp!(isize, usize);
}

macro_rules! check_nonzero_clamp {
($t:ty, $ut:ty) => {
let min = <$t>::MIN;
let max = <$t>::MAX;
let max_u = <$ut>::MAX;

// Basic clamping
let lim = <$ut>::new(50).unwrap();
assert_eq!(<$t>::new(100).unwrap().clamp_magnitude(lim), <$t>::new(50).unwrap());
assert_eq!(<$t>::new(-100).unwrap().clamp_magnitude(lim), <$t>::new(-50).unwrap());
assert_eq!(<$t>::new(30).unwrap().clamp_magnitude(lim), <$t>::new(30).unwrap());
assert_eq!(<$t>::new(-30).unwrap().clamp_magnitude(lim), <$t>::new(-30).unwrap());

// Exact boundary
assert_eq!(<$t>::new(50).unwrap().clamp_magnitude(lim), <$t>::new(50).unwrap());
assert_eq!(<$t>::new(-50).unwrap().clamp_magnitude(lim), <$t>::new(-50).unwrap());

// MIN/MAX values
// Symmetric range [-MAX, MAX]
assert_eq!(max.clamp_magnitude(max.try_into().unwrap()), max);
assert_eq!(min.clamp_magnitude(max.try_into().unwrap()), -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);

// Const clamping
const LIM: $ut = <$ut>::new(50).unwrap();
const C1: $t = <$t>::new(100).unwrap().clamp_magnitude(LIM);
assert_eq!(C1, <$t>::new(50).unwrap());
const C2: $t = <$t>::new(-100).unwrap().clamp_magnitude(LIM);
assert_eq!(C2, <$t>::new(-50).unwrap());
const C3: $t = <$t>::new(30).unwrap().clamp_magnitude(LIM);
assert_eq!(C3, <$t>::new(30).unwrap());
const C4: $t = <$t>::new(-30).unwrap().clamp_magnitude(LIM);
assert_eq!(C4, <$t>::new(-30).unwrap());
};
}

#[test]
fn test_clamp_magnitude_nonzero_i8() {
check_nonzero_clamp!(NonZeroI8, NonZeroU8);
}

#[test]
fn test_clamp_magnitude_nonzero_i16() {
check_nonzero_clamp!(NonZeroI16, NonZeroU16);
}

#[test]
fn test_clamp_magnitude_nonzero_i32() {
check_nonzero_clamp!(NonZeroI32, NonZeroU32);
}

#[test]
fn test_clamp_magnitude_nonzero_i64() {
check_nonzero_clamp!(NonZeroI64, NonZeroU64);
}

#[test]
fn test_clamp_magnitude_nonzero_i128() {
check_nonzero_clamp!(NonZeroI128, NonZeroU128);
}

#[test]
fn test_clamp_magnitude_nonzero_isize() {
check_nonzero_clamp!(NonZeroIsize, NonZeroUsize);
}

macro_rules! check_float_clamp {
($t:ty) => {
($t:ty, $huge: expr) => {
// 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);
Expand All @@ -78,10 +165,13 @@ macro_rules! check_float_clamp {
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);
type Float = $t;
assert_biteq!((0.0 as $t).clamp_magnitude(1.0), 0.0);
assert_biteq!((-0.0 as $t).clamp_magnitude(1.0), -0.0);
assert_biteq!((5.0 as $t).clamp_magnitude(0.0), 0.0);
assert_biteq!((-5.0 as $t).clamp_magnitude(0.0), -0.0);
assert_biteq!((5.0 as $t).clamp_magnitude(-0.0), 0.0);
assert_biteq!((-5.0 as $t).clamp_magnitude(-0.0), -0.0);

// Special values - Infinity
let inf = <$t>::INFINITY;
Expand All @@ -98,42 +188,95 @@ macro_rules! check_float_clamp {
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);
assert_eq!(max.clamp_magnitude($huge), $huge);
assert_eq!(min.clamp_magnitude($huge), -$huge);

// NaN
let nan = <$t>::NAN;
assert!(nan.clamp_magnitude(1.0).is_nan());

// Const clamping
const C1: $t = (5.0 as $t).clamp_magnitude(3.0);
assert_eq!(C1, 3.0);
const C2: $t = (-5.0 as $t).clamp_magnitude(3.0);
assert_eq!(C2, -3.0);
const C3: $t = (2.0 as $t).clamp_magnitude(3.0);
assert_eq!(C3, 2.0);
const C4: $t = (-2.0 as $t).clamp_magnitude(3.0);
assert_eq!(C4, -2.0);
};
}

#[test]
#[cfg(target_has_reliable_f16)]
fn test_clamp_magnitude_f16() {
check_float_clamp!(f16, 1e3);
}

#[test]
fn test_clamp_magnitude_f32() {
check_float_clamp!(f32);
check_float_clamp!(f32, 1e30);
}

#[test]
fn test_clamp_magnitude_f64() {
check_float_clamp!(f64);
check_float_clamp!(f64, 1e300);
}

#[test]
#[cfg(target_has_reliable_f128)]
fn test_clamp_magnitude_f128() {
check_float_clamp!(f128, 1e3000);
}

#[test]
#[should_panic(expected = "limit must be non-negative")]
#[cfg(target_has_reliable_f16)]
#[should_panic(expected = "limit must be non-negative and not NaN")]
fn test_clamp_magnitude_f16_panic_negative_limit() {
let _ = 1.0f16.clamp_magnitude(-1.0);
}

#[test]
#[should_panic(expected = "limit must be non-negative and not NaN")]
fn test_clamp_magnitude_f32_panic_negative_limit() {
Comment thread
Eleocraft marked this conversation as resolved.
Resolved
let _ = 1.0f32.clamp_magnitude(-1.0);
}

#[test]
#[should_panic(expected = "limit must be non-negative")]
#[should_panic(expected = "limit must be non-negative and not NaN")]
fn test_clamp_magnitude_f64_panic_negative_limit() {
let _ = 1.0f64.clamp_magnitude(-1.0);
}

#[test]
#[should_panic]
#[cfg(target_has_reliable_f128)]
#[should_panic(expected = "limit must be non-negative and not NaN")]
fn test_clamp_magnitude_f128_panic_negative_limit() {
let _ = 1.0f128.clamp_magnitude(-1.0);
}

#[test]
#[cfg(target_has_reliable_f16)]
#[should_panic(expected = "limit must be non-negative and not NaN")]
fn test_clamp_magnitude_f16_panic_nan_limit() {
let _ = 1.0f16.clamp_magnitude(f16::NAN);
}

#[test]
#[should_panic(expected = "limit must be non-negative and not NaN")]
fn test_clamp_magnitude_f32_panic_nan_limit() {
let _ = 1.0f32.clamp_magnitude(f32::NAN);
}

#[test]
#[should_panic]
#[should_panic(expected = "limit must be non-negative and not NaN")]
fn test_clamp_magnitude_f64_panic_nan_limit() {
let _ = 1.0f64.clamp_magnitude(f64::NAN);
}

#[test]
#[cfg(target_has_reliable_f128)]
#[should_panic(expected = "limit must be non-negative and not NaN")]
fn test_clamp_magnitude_f128_panic_nan_limit() {
let _ = 1.0f128.clamp_magnitude(f128::NAN);
}
1 change: 1 addition & 0 deletions library/coretests/tests/num/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ mod u8;
mod bignum;
mod carryless_mul;
mod cast;
mod clamp_magnitude;
mod complex;
mod const_from;
mod dec2flt;
Expand Down
Loading