From c140bc107ce10bc4ea491f6a02091163439f4132 Mon Sep 17 00:00:00 2001 From: Alexandre Courbot Date: Sun, 10 Aug 2025 22:19:32 +0900 Subject: [PATCH] Implement feature `int_lowest_highest_one` for integer and NonZero types Implement the accepted ACP for methods that find the index of the least significant (lowest) and most significant (highest) set bit in an integer for signed, unsigned, and NonZero types. Also add unit tests for all these types. --- library/core/src/num/int_macros.rs | 42 ++++++++ library/core/src/num/nonzero.rs | 50 +++++++++ library/core/src/num/uint_macros.rs | 48 +++++++++ library/coretests/tests/lib.rs | 1 + library/coretests/tests/nonzero.rs | 118 +++++++++++++++++++++ library/coretests/tests/num/int_macros.rs | 40 +++++++ library/coretests/tests/num/uint_macros.rs | 34 ++++++ 7 files changed, 333 insertions(+) diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index bd2f7445612a7..a4efbaf6ad23c 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -209,6 +209,48 @@ macro_rules! int_impl { self & self.wrapping_neg() } + /// Returns the index of the highest bit set to one in `self`, or `None` + /// if `self` is `0`. + /// + /// # Examples + /// + /// ``` + /// #![feature(int_lowest_highest_one)] + /// + #[doc = concat!("assert_eq!(0x0_", stringify!($SelfT), ".highest_one(), None);")] + #[doc = concat!("assert_eq!(0x1_", stringify!($SelfT), ".highest_one(), Some(0));")] + #[doc = concat!("assert_eq!(0x10_", stringify!($SelfT), ".highest_one(), Some(4));")] + #[doc = concat!("assert_eq!(0x1f_", stringify!($SelfT), ".highest_one(), Some(4));")] + /// ``` + #[unstable(feature = "int_lowest_highest_one", issue = "145203")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline(always)] + pub const fn highest_one(self) -> Option { + (self as $UnsignedT).highest_one() + } + + /// Returns the index of the lowest bit set to one in `self`, or `None` + /// if `self` is `0`. + /// + /// # Examples + /// + /// ``` + /// #![feature(int_lowest_highest_one)] + /// + #[doc = concat!("assert_eq!(0x0_", stringify!($SelfT), ".lowest_one(), None);")] + #[doc = concat!("assert_eq!(0x1_", stringify!($SelfT), ".lowest_one(), Some(0));")] + #[doc = concat!("assert_eq!(0x10_", stringify!($SelfT), ".lowest_one(), Some(4));")] + #[doc = concat!("assert_eq!(0x1f_", stringify!($SelfT), ".lowest_one(), Some(0));")] + /// ``` + #[unstable(feature = "int_lowest_highest_one", issue = "145203")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline(always)] + pub const fn lowest_one(self) -> Option { + (self as $UnsignedT).lowest_one() + } + /// Returns the bit pattern of `self` reinterpreted as an unsigned integer of the same size. /// /// This produces the same result as an `as` cast, but ensures that the bit-width remains diff --git a/library/core/src/num/nonzero.rs b/library/core/src/num/nonzero.rs index 308d722f5d564..3860b57029662 100644 --- a/library/core/src/num/nonzero.rs +++ b/library/core/src/num/nonzero.rs @@ -681,6 +681,56 @@ macro_rules! nonzero_integer { unsafe { NonZero::new_unchecked(n) } } + /// Returns the index of the highest bit set to one in `self`, or `None` + /// if `self` is `0`. + /// + /// # Examples + /// + /// ``` + /// #![feature(int_lowest_highest_one)] + /// + /// # use core::num::NonZero; + /// # fn main() { test().unwrap(); } + /// # fn test() -> Option<()> { + #[doc = concat!("assert_eq!(NonZero::<", stringify!($Int), ">::new(0x1)?.highest_one(), 0);")] + #[doc = concat!("assert_eq!(NonZero::<", stringify!($Int), ">::new(0x10)?.highest_one(), 4);")] + #[doc = concat!("assert_eq!(NonZero::<", stringify!($Int), ">::new(0x1f)?.highest_one(), 4);")] + /// # Some(()) + /// # } + /// ``` + #[unstable(feature = "int_lowest_highest_one", issue = "145203")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline(always)] + pub const fn highest_one(self) -> u32 { + Self::BITS - 1 - self.leading_zeros() + } + + /// Returns the index of the lowest bit set to one in `self`, or `None` + /// if `self` is `0`. + /// + /// # Examples + /// + /// ``` + /// #![feature(int_lowest_highest_one)] + /// + /// # use core::num::NonZero; + /// # fn main() { test().unwrap(); } + /// # fn test() -> Option<()> { + #[doc = concat!("assert_eq!(NonZero::<", stringify!($Int), ">::new(0x1)?.lowest_one(), 0);")] + #[doc = concat!("assert_eq!(NonZero::<", stringify!($Int), ">::new(0x10)?.lowest_one(), 4);")] + #[doc = concat!("assert_eq!(NonZero::<", stringify!($Int), ">::new(0x1f)?.lowest_one(), 0);")] + /// # Some(()) + /// # } + /// ``` + #[unstable(feature = "int_lowest_highest_one", issue = "145203")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline(always)] + pub const fn lowest_one(self) -> u32 { + self.trailing_zeros() + } + /// Returns the number of ones in the binary representation of `self`. /// /// # Examples diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index 2c9a119684963..34638f9a799e5 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -261,6 +261,54 @@ macro_rules! uint_impl { self & self.wrapping_neg() } + /// Returns the index of the highest bit set to one in `self`, or `None` + /// if `self` is `0`. + /// + /// # Examples + /// + /// ``` + /// #![feature(int_lowest_highest_one)] + /// + #[doc = concat!("assert_eq!(0x0_", stringify!($SelfT), ".highest_one(), None);")] + #[doc = concat!("assert_eq!(0x1_", stringify!($SelfT), ".highest_one(), Some(0));")] + #[doc = concat!("assert_eq!(0x10_", stringify!($SelfT), ".highest_one(), Some(4));")] + #[doc = concat!("assert_eq!(0x1f_", stringify!($SelfT), ".highest_one(), Some(4));")] + /// ``` + #[unstable(feature = "int_lowest_highest_one", issue = "145203")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline(always)] + pub const fn highest_one(self) -> Option { + match NonZero::new(self) { + Some(v) => Some(v.highest_one()), + None => None, + } + } + + /// Returns the index of the lowest bit set to one in `self`, or `None` + /// if `self` is `0`. + /// + /// # Examples + /// + /// ``` + /// #![feature(int_lowest_highest_one)] + /// + #[doc = concat!("assert_eq!(0x0_", stringify!($SelfT), ".lowest_one(), None);")] + #[doc = concat!("assert_eq!(0x1_", stringify!($SelfT), ".lowest_one(), Some(0));")] + #[doc = concat!("assert_eq!(0x10_", stringify!($SelfT), ".lowest_one(), Some(4));")] + #[doc = concat!("assert_eq!(0x1f_", stringify!($SelfT), ".lowest_one(), Some(0));")] + /// ``` + #[unstable(feature = "int_lowest_highest_one", issue = "145203")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline(always)] + pub const fn lowest_one(self) -> Option { + match NonZero::new(self) { + Some(v) => Some(v.lowest_one()), + None => None, + } + } + /// Returns the bit pattern of `self` reinterpreted as a signed integer of the same size. /// /// This produces the same result as an `as` cast, but ensures that the bit-width remains diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 0c4d49f3c994b..3781285686da3 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -54,6 +54,7 @@ #![feature(generic_assert_internals)] #![feature(hasher_prefixfree_extras)] #![feature(hashmap_internals)] +#![feature(int_lowest_highest_one)] #![feature(int_roundings)] #![feature(ip)] #![feature(ip_from)] diff --git a/library/coretests/tests/nonzero.rs b/library/coretests/tests/nonzero.rs index eb06c34fd0205..726648363f4dc 100644 --- a/library/coretests/tests/nonzero.rs +++ b/library/coretests/tests/nonzero.rs @@ -462,3 +462,121 @@ fn test_nonzero_fmt() { assert_eq!(i, nz); } + +#[test] +fn test_nonzero_highest_one() { + macro_rules! nonzero_int_impl { + ($($T:ty),+) => { + $( + { + const ONE: $T = 1; + const MINUS_ONE: $T = -1; + + for i in 0..<$T>::BITS { + // Set single bit. + assert_eq!(NonZero::<$T>::new(ONE << i).unwrap().highest_one(), i); + if i > <$T>::BITS { + // Set lowest bits. + assert_eq!( + NonZero::<$T>::new(<$T>::MAX >> i).unwrap().highest_one(), + <$T>::BITS - i - 2, + ); + } + // Set highest bits. + assert_eq!( + NonZero::<$T>::new(MINUS_ONE << i).unwrap().highest_one(), + <$T>::BITS - 1, + ); + } + } + )+ + }; + } + + macro_rules! nonzero_uint_impl { + ($($T:ty),+) => { + $( + { + const ONE: $T = 1; + + for i in 0..<$T>::BITS { + // Set single bit. + assert_eq!(NonZero::<$T>::new(ONE << i).unwrap().highest_one(), i); + // Set lowest bits. + assert_eq!( + NonZero::<$T>::new(<$T>::MAX >> i).unwrap().highest_one(), + <$T>::BITS - i - 1, + ); + // Set highest bits. + assert_eq!( + NonZero::<$T>::new(<$T>::MAX << i).unwrap().highest_one(), + <$T>::BITS - 1, + ); + } + } + )+ + }; + } + + nonzero_int_impl!(i8, i16, i32, i64, i128, isize); + nonzero_uint_impl!(u8, u16, u32, u64, u128, usize); +} + +#[test] +fn test_nonzero_lowest_one() { + macro_rules! nonzero_int_impl { + ($($T:ty),+) => { + $( + { + const ONE: $T = 1; + const MINUS_ONE: $T = -1; + + for i in 0..<$T>::BITS { + // Set single bit. + assert_eq!(NonZero::<$T>::new(ONE << i).unwrap().lowest_one(), i); + if i > <$T>::BITS { + // Set lowest bits. + assert_eq!( + NonZero::<$T>::new(<$T>::MAX >> i).unwrap().lowest_one(), + 0, + ); + } + // Set highest bits. + assert_eq!( + NonZero::<$T>::new(MINUS_ONE << i).unwrap().lowest_one(), + i, + ); + } + } + )+ + }; + } + + macro_rules! nonzero_uint_impl { + ($($T:ty),+) => { + $( + { + const ONE: $T = 1; + + for i in 0..<$T>::BITS { + // Set single bit. + assert_eq!(NonZero::<$T>::new(ONE << i).unwrap().lowest_one(), i); + // Set lowest bits. + assert_eq!( + NonZero::<$T>::new(<$T>::MAX >> i).unwrap().lowest_one(), + 0, + ); + // Set highest bits. + assert_eq!( + NonZero::<$T>::new(<$T>::MAX << i).unwrap().lowest_one(), + i, + ); + } + } + )+ + }; + } + + nonzero_int_impl!(i8, i16, i32, i64, i128, isize); + nonzero_uint_impl!(u8, u16, u32, u64, u128, usize); +} diff --git a/library/coretests/tests/num/int_macros.rs b/library/coretests/tests/num/int_macros.rs index ca32fce861f8c..1611a6466f5ab 100644 --- a/library/coretests/tests/num/int_macros.rs +++ b/library/coretests/tests/num/int_macros.rs @@ -227,6 +227,46 @@ macro_rules! int_module { } } + #[test] + fn test_highest_one() { + const ZERO: $T = 0; + const ONE: $T = 1; + const MINUS_ONE: $T = -1; + + assert_eq!(ZERO.highest_one(), None); + + for i in 0..<$T>::BITS { + // Set single bit. + assert_eq!((ONE << i).highest_one(), Some(i)); + if i != <$T>::BITS - 1 { + // Set lowest bits. + assert_eq!((<$T>::MAX >> i).highest_one(), Some(<$T>::BITS - i - 2)); + } + // Set highest bits. + assert_eq!((MINUS_ONE << i).highest_one(), Some(<$T>::BITS - 1)); + } + } + + #[test] + fn test_lowest_one() { + const ZERO: $T = 0; + const ONE: $T = 1; + const MINUS_ONE: $T = -1; + + assert_eq!(ZERO.lowest_one(), None); + + for i in 0..<$T>::BITS { + // Set single bit. + assert_eq!((ONE << i).lowest_one(), Some(i)); + if i != <$T>::BITS - 1 { + // Set lowest bits. + assert_eq!((<$T>::MAX >> i).lowest_one(), Some(0)); + } + // Set highest bits. + assert_eq!((MINUS_ONE << i).lowest_one(), Some(i)); + } + } + #[test] fn test_from_str() { fn from_str(t: &str) -> Option { diff --git a/library/coretests/tests/num/uint_macros.rs b/library/coretests/tests/num/uint_macros.rs index 8f389de70aa2e..c7d10ea4d880a 100644 --- a/library/coretests/tests/num/uint_macros.rs +++ b/library/coretests/tests/num/uint_macros.rs @@ -184,6 +184,40 @@ macro_rules! uint_module { } } + #[test] + fn test_highest_one() { + const ZERO: $T = 0; + const ONE: $T = 1; + + assert_eq!(ZERO.highest_one(), None); + + for i in 0..<$T>::BITS { + // Set single bit. + assert_eq!((ONE << i).highest_one(), Some(i)); + // Set lowest bits. + assert_eq!((<$T>::MAX >> i).highest_one(), Some(<$T>::BITS - i - 1)); + // Set highest bits. + assert_eq!((<$T>::MAX << i).highest_one(), Some(<$T>::BITS - 1)); + } + } + + #[test] + fn test_lowest_one() { + const ZERO: $T = 0; + const ONE: $T = 1; + + assert_eq!(ZERO.lowest_one(), None); + + for i in 0..<$T>::BITS { + // Set single bit. + assert_eq!((ONE << i).lowest_one(), Some(i)); + // Set lowest bits. + assert_eq!((<$T>::MAX >> i).lowest_one(), Some(0)); + // Set highest bits. + assert_eq!((<$T>::MAX << i).lowest_one(), Some(i)); + } + } + fn from_str(t: &str) -> Option { core::str::FromStr::from_str(t).ok() }