From 9f579968cd4a7544eae0b76859089d78b418f131 Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Thu, 24 Jun 2021 13:01:17 +0200 Subject: [PATCH] Add Integer::{log,log2,log10} variants --- library/core/src/num/int_macros.rs | 188 ++++++++++++++++++++++++++++ library/core/src/num/uint_macros.rs | 188 ++++++++++++++++++++++++++++ library/core/tests/lib.rs | 1 + library/core/tests/num/int_log.rs | 99 +++++++++++++++ library/core/tests/num/mod.rs | 1 + library/std/src/lib.rs | 1 + 6 files changed, 478 insertions(+) create mode 100644 library/core/tests/num/int_log.rs diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index 2e466106fe5..a9461649d4a 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -1744,6 +1744,194 @@ pub const fn rem_euclid(self, rhs: Self) -> Self { } } + /// Returns the logarithm of the number with respect to an arbitrary base. + /// + /// This method may not be optimized owing to implementation details; + /// `log2` can produce results more efficiently for base 2, and `log10` + /// can produce results more efficiently for base 10. + /// + /// # Panics + /// + /// When the number is zero, or if the base is not at least 2; it + /// panics in debug mode and the return value is wrapped to 0 in release + /// mode (the only situation in which the method can return 0). + /// + /// # Examples + /// + /// ``` + /// #![feature(int_log)] + #[doc = concat!("assert_eq!(5", stringify!($SelfT), ".log(5), 1);")] + /// ``` + #[unstable(feature = "int_log", issue = "70887")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + #[track_caller] + #[rustc_inherit_overflow_checks] + #[allow(arithmetic_overflow)] + pub const fn log(self, base: Self) -> Self { + match self.checked_log(base) { + Some(n) => n, + None => { + // In debug builds, trigger a panic on None. + // This should optimize completely out in release builds. + let _ = Self::MAX + 1; + + 0 + }, + } + } + + /// Returns the base 2 logarithm of the number. + /// + /// # Panics + /// + /// When the number is zero it panics in debug mode and the return value + /// is wrapped to 0 in release mode (the only situation in which the + /// method can return 0). + /// + /// # Examples + /// + /// ``` + /// #![feature(int_log)] + #[doc = concat!("assert_eq!(2", stringify!($SelfT), ".log2(), 1);")] + /// ``` + #[unstable(feature = "int_log", issue = "70887")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + #[track_caller] + #[rustc_inherit_overflow_checks] + #[allow(arithmetic_overflow)] + pub const fn log2(self) -> Self { + match self.checked_log2() { + Some(n) => n, + None => { + // In debug builds, trigger a panic on None. + // This should optimize completely out in release builds. + let _ = Self::MAX + 1; + + 0 + }, + } + } + + /// Returns the base 10 logarithm of the number. + /// + /// # Panics + /// + /// When the number is zero it panics in debug mode and the return value + /// is wrapped to 0 in release mode (the only situation in which the + /// method can return 0). + /// + /// # Example + /// + /// ``` + /// #![feature(int_log)] + #[doc = concat!("assert_eq!(10", stringify!($SelfT), ".log10(), 1);")] + /// ``` + #[unstable(feature = "int_log", issue = "70887")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + #[track_caller] + #[rustc_inherit_overflow_checks] + #[allow(arithmetic_overflow)] + pub const fn log10(self) -> Self { + match self.checked_log10() { + Some(n) => n, + None => { + // In debug builds, trigger a panic on None. + // This should optimize completely out in release builds. + let _ = Self::MAX + 1; + + 0 + }, + } + } + + /// Returns the logarithm of the number with respect to an arbitrary base. + /// + /// Returns `None` if the number is negative or zero, or if the base is not at least 2. + /// + /// This method may not be optimized owing to implementation details; + /// `checked_log2` can produce results more efficiently for base 2, and + /// `checked_log10` can produce results more efficiently for base 10. + /// + /// # Examples + /// + /// ``` + /// #![feature(int_log)] + #[doc = concat!("assert_eq!(5", stringify!($SelfT), ".checked_log(5), Some(1));")] + /// ``` + #[unstable(feature = "int_log", issue = "70887")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + pub const fn checked_log(self, base: Self) -> Option { + if self <= 0 || base <= 1 { + None + } else { + let mut n = 0; + let mut r = self; + + // Optimization for 128 bit wide integers. + if Self::BITS == 128 { + let b = Self::log2(self) / (Self::log2(base) + 1); + n += b; + r /= base.pow(b as u32); + } + + while r >= base { + r /= base; + n += 1; + } + Some(n) + } + } + + /// Returns the base 2 logarithm of the number. + /// + /// Returns `None` if the number is negative or zero. + /// + /// # Examples + /// + /// ``` + /// #![feature(int_log)] + #[doc = concat!("assert_eq!(2", stringify!($SelfT), ".checked_log2(), Some(1));")] + /// ``` + #[unstable(feature = "int_log", issue = "70887")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + pub const fn checked_log2(self) -> Option { + if self <= 0 { + None + } else { + // SAFETY: We just checked that this number is positive + let log = (Self::BITS - 1) as Self - unsafe { intrinsics::ctlz_nonzero(self) }; + Some(log) + } + } + + /// Returns the base 10 logarithm of the number. + /// + /// Returns `None` if the number is negative or zero. + /// + /// # Example + /// + /// ``` + /// #![feature(int_log)] + #[doc = concat!("assert_eq!(10", stringify!($SelfT), ".checked_log10(), Some(1));")] + /// ``` + #[unstable(feature = "int_log", issue = "70887")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + pub const fn checked_log10(self) -> Option { + self.checked_log(10) + } + /// Computes the absolute value of `self`. /// /// # Overflow behavior diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index e512d90ef37..bf4d2e7433e 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -634,6 +634,194 @@ pub const fn checked_rem_euclid(self, rhs: Self) -> Option { } } + /// Returns the logarithm of the number with respect to an arbitrary base. + /// + /// This method may not be optimized owing to implementation details; + /// `log2` can produce results more efficiently for base 2, and `log10` + /// can produce results more efficiently for base 10. + /// + /// # Panics + /// + /// When the number is negative, zero, or if the base is not at least 2; + /// it panics in debug mode and the return value is wrapped to 0 in + /// release mode (the only situation in which the method can return 0). + /// + /// # Examples + /// + /// ``` + /// #![feature(int_log)] + #[doc = concat!("assert_eq!(5", stringify!($SelfT), ".log(5), 1);")] + /// ``` + #[unstable(feature = "int_log", issue = "70887")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + #[track_caller] + #[rustc_inherit_overflow_checks] + #[allow(arithmetic_overflow)] + pub const fn log(self, base: Self) -> Self { + match self.checked_log(base) { + Some(n) => n, + None => { + // In debug builds, trigger a panic on None. + // This should optimize completely out in release builds. + let _ = Self::MAX + 1; + + 0 + }, + } + } + + /// Returns the base 2 logarithm of the number. + /// + /// # Panics + /// + /// When the number is negative or zero it panics in debug mode and + /// the return value is wrapped to 0 in release mode (the only situation in + /// which the method can return 0). + /// + /// # Examples + /// + /// ``` + /// #![feature(int_log)] + #[doc = concat!("assert_eq!(2", stringify!($SelfT), ".log2(), 1);")] + /// ``` + #[unstable(feature = "int_log", issue = "70887")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + #[track_caller] + #[rustc_inherit_overflow_checks] + #[allow(arithmetic_overflow)] + pub const fn log2(self) -> Self { + match self.checked_log2() { + Some(n) => n, + None => { + // In debug builds, trigger a panic on None. + // This should optimize completely out in release builds. + let _ = Self::MAX + 1; + + 0 + }, + } + } + + /// Returns the base 10 logarithm of the number. + /// + /// # Panics + /// + /// When the number is negative or zero it panics in debug mode and the + /// return value is wrapped to 0 in release mode (the only situation in + /// which the method can return 0). + /// + /// # Example + /// + /// ``` + /// #![feature(int_log)] + #[doc = concat!("assert_eq!(10", stringify!($SelfT), ".log10(), 1);")] + /// ``` + #[unstable(feature = "int_log", issue = "70887")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + #[track_caller] + #[rustc_inherit_overflow_checks] + #[allow(arithmetic_overflow)] + pub const fn log10(self) -> Self { + match self.checked_log10() { + Some(n) => n, + None => { + // In debug builds, trigger a panic on None. + // This should optimize completely out in release builds. + let _ = Self::MAX + 1; + + 0 + }, + } + } + + /// Returns the logarithm of the number with respect to an arbitrary base. + /// + /// Returns `None` if the number is zero, or if the base is not at least 2. + /// + /// This method may not be optimized owing to implementation details; + /// `checked_log2` can produce results more efficiently for base 2, and + /// `checked_log10` can produce results more efficiently for base 10. + /// + /// # Examples + /// + /// ``` + /// #![feature(int_log)] + #[doc = concat!("assert_eq!(5", stringify!($SelfT), ".checked_log(5), Some(1));")] + /// ``` + #[unstable(feature = "int_log", issue = "70887")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + pub const fn checked_log(self, base: Self) -> Option { + if self <= 0 || base <= 1 { + None + } else { + let mut n = 0; + let mut r = self; + + // Optimization for 128 bit wide integers. + if Self::BITS == 128 { + let b = Self::log2(self) / (Self::log2(base) + 1); + n += b; + r /= base.pow(b as u32); + } + + while r >= base { + r /= base; + n += 1; + } + Some(n) + } + } + + /// Returns the base 2 logarithm of the number. + /// + /// Returns `None` if the number is zero. + /// + /// # Examples + /// + /// ``` + /// #![feature(int_log)] + #[doc = concat!("assert_eq!(2", stringify!($SelfT), ".checked_log2(), Some(1));")] + /// ``` + #[unstable(feature = "int_log", issue = "70887")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + pub const fn checked_log2(self) -> Option { + if self <= 0 { + None + } else { + // SAFETY: We just checked that this number is positive + let log = (Self::BITS - 1) as Self - unsafe { intrinsics::ctlz_nonzero(self) }; + Some(log) + } + } + + /// Returns the base 10 logarithm of the number. + /// + /// Returns `None` if the number is zero. + /// + /// # Examples + /// + /// ``` + /// #![feature(int_log)] + #[doc = concat!("assert_eq!(10", stringify!($SelfT), ".checked_log10(), Some(1));")] + /// ``` + #[unstable(feature = "int_log", issue = "70887")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + pub const fn checked_log10(self) -> Option { + self.checked_log(10) + } + /// Checked negation. Computes `-self`, returning `None` unless `self == /// 0`. /// diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs index 56af3848584..62491cc4d92 100644 --- a/library/core/tests/lib.rs +++ b/library/core/tests/lib.rs @@ -45,6 +45,7 @@ #![feature(try_trait_v2)] #![feature(slice_internals)] #![feature(slice_partition_dedup)] +#![feature(int_log)] #![feature(iter_advance_by)] #![feature(iter_partition_in_place)] #![feature(iter_intersperse)] diff --git a/library/core/tests/num/int_log.rs b/library/core/tests/num/int_log.rs new file mode 100644 index 00000000000..99a9b17ab11 --- /dev/null +++ b/library/core/tests/num/int_log.rs @@ -0,0 +1,99 @@ +//! This tests the `Integer::{log,log2,log10}` methods. These tests are in a +//! separate file because there's both a large number of them, and not all tests +//! can be run on Android. This is because in Android `log2` uses an imprecise +//! approximation:https://github.com/rust-lang/rust/blob/4825e12fc9c79954aa0fe18f5521efa6c19c7539/src/libstd/sys/unix/android.rs#L27-L53 + +#[test] +fn checked_log() { + assert_eq!(999u32.checked_log(10), Some(2)); + assert_eq!(1000u32.checked_log(10), Some(3)); + assert_eq!(555u32.checked_log(13), Some(2)); + assert_eq!(63u32.checked_log(4), Some(2)); + assert_eq!(64u32.checked_log(4), Some(3)); + assert_eq!(10460353203u64.checked_log(3), Some(21)); + assert_eq!(10460353202u64.checked_log(3), Some(20)); + assert_eq!(147808829414345923316083210206383297601u128.checked_log(3), Some(80)); + assert_eq!(147808829414345923316083210206383297600u128.checked_log(3), Some(79)); + assert_eq!(22528399544939174411840147874772641u128.checked_log(19683), Some(8)); + assert_eq!(22528399544939174411840147874772631i128.checked_log(19683), Some(7)); + + assert_eq!(0u8.checked_log(4), None); + assert_eq!(0u16.checked_log(4), None); + assert_eq!(0i8.checked_log(4), None); + assert_eq!(0i16.checked_log(4), None); + + for i in i16::MIN..=0 { + assert_eq!(i.checked_log(4), None); + } + for i in 1..=i16::MAX { + assert_eq!(i.checked_log(13), Some((i as f32).log(13.0) as i16)); + } + for i in 1..=u16::MAX { + assert_eq!(i.checked_log(13), Some((i as f32).log(13.0) as u16)); + } +} + +#[test] +fn checked_log2() { + assert_eq!(5u32.checked_log2(), Some(2)); + assert_eq!(0u64.checked_log2(), None); + assert_eq!(128i32.checked_log2(), Some(7)); + assert_eq!((-55i16).checked_log2(), None); + + assert_eq!(0u8.checked_log2(), None); + assert_eq!(0u16.checked_log2(), None); + assert_eq!(0i8.checked_log2(), None); + assert_eq!(0i16.checked_log2(), None); + + for i in 1..=u8::MAX { + assert_eq!(i.checked_log2(), Some((i as f32).log2() as u8)); + } + for i in 1..=u16::MAX { + // Guard against Android's imprecise f32::log2 implementation. + if i != 8192 && i != 32768 { + assert_eq!(i.checked_log2(), Some((i as f32).log2() as u16)); + } + } + for i in i8::MIN..=0 { + assert_eq!(i.checked_log2(), None); + } + for i in 1..=i8::MAX { + assert_eq!(i.checked_log2(), Some((i as f32).log2() as i8)); + } + for i in i16::MIN..=0 { + assert_eq!(i.checked_log2(), None); + } + for i in 1..=i16::MAX { + // Guard against Android's imprecise f32::log2 implementation. + if i != 8192 { + assert_eq!(i.checked_log2(), Some((i as f32).log2() as i16)); + } + } +} + +// Validate cases that fail on Android's imprecise float log2 implementation. +#[test] +#[cfg(not(target_os = "android"))] +fn checked_log2_not_android() { + assert_eq!(8192u16.checked_log2(), Some((8192f32).log2() as u16)); + assert_eq!(32768u16.checked_log2(), Some((32768f32).log2() as u16)); + assert_eq!(8192i16.checked_log2(), Some((8192f32).log2() as i16)); +} + +#[test] +fn checked_log10() { + assert_eq!(0u8.checked_log10(), None); + assert_eq!(0u16.checked_log10(), None); + assert_eq!(0i8.checked_log10(), None); + assert_eq!(0i16.checked_log10(), None); + + for i in i16::MIN..=0 { + assert_eq!(i.checked_log10(), None); + } + for i in 1..=i16::MAX { + assert_eq!(i.checked_log10(), Some((i as f32).log10() as i16)); + } + for i in 1..=u16::MAX { + assert_eq!(i.checked_log10(), Some((i as f32).log10() as u16)); + } +} diff --git a/library/core/tests/num/mod.rs b/library/core/tests/num/mod.rs index bbb67667dfc..76e838cf6bf 100644 --- a/library/core/tests/num/mod.rs +++ b/library/core/tests/num/mod.rs @@ -29,6 +29,7 @@ mod bignum; mod dec2flt; mod flt2dec; +mod int_log; mod ops; mod wrapping; diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 1cfa71e250f..5419262b516 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -279,6 +279,7 @@ #![feature(hashmap_internals)] #![feature(int_error_internals)] #![feature(integer_atomics)] +#![feature(int_log)] #![feature(into_future)] #![feature(intra_doc_pointers)] #![feature(iter_zip)]