mirror of
https://github.com/rust-lang/rust.git
synced 2025-01-18 02:34:37 +00:00
Rollup merge of #80918 - yoshuawuyts:int-log2, r=m-ou-se
Add Integer::log variants _This is another attempt at landing https://github.com/rust-lang/rust/pull/70835, which was approved by the libs team but failed on Android tests through Bors. The text copied here is from the original issue. The only change made so far is the addition of non-`checked_` variants of the log methods._ _Tracking issue: #70887_ --- This implements `{log,log2,log10}` methods for all integer types. The implementation was provided by `@substack` for use in the stdlib. _Note: I'm not big on math, so this PR is a best effort written with limited knowledge. It's likely I'll be getting things wrong, but happy to learn and correct. Please bare with me._ ## Motivation Calculating the logarithm of a number is a generally useful operation. Currently the stdlib only provides implementations for floats, which means that if we want to calculate the logarithm for an integer we have to cast it to a float and then back to an int. > would be nice if there was an integer log2 instead of having to either use the f32 version or leading_zeros() which i have to verify the results of every time to be sure _— [`@substack,` 2020-03-08](https://twitter.com/substack/status/1236445105197727744)_ At higher numbers converting from an integer to a float we also risk overflows. This means that Rust currently only provides log operations for a limited set of integers. The process of doing log operations by converting between floats and integers is also prone to rounding errors. In the following example we're trying to calculate `base10` for an integer. We might try and calculate the `base2` for the values, and attempt [a base swap](https://www.rapidtables.com/math/algebra/Logarithm.html#log-rules) to arrive at `base10`. However because we're performing intermediate rounding we arrive at the wrong result: ```rust // log10(900) = ~2.95 = 2 dbg!(900f32.log10() as u64); // log base change rule: logb(x) = logc(x) / logc(b) // log2(900) / log2(10) = 9/3 = 3 dbg!((900f32.log2() as u64) / (10f32.log2() as u64)); ``` _[playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6bd6c68b3539e400f9ca4fdc6fc2eed0)_ This is somewhat nuanced as a lot of the time it'll work well, but in real world code this could lead to some hard to track bugs. By providing correct log implementations directly on integers we can help prevent errors around this. ## Implementation notes I checked whether LLVM intrinsics existed before implementing this, and none exist yet. ~~Also I couldn't really find a better way to write the `ilog` function. One option would be to make it a private method on the number, but I didn't see any precedent for that. I also didn't know where to best place the tests, so I added them to the bottom of the file. Even though they might seem like quite a lot they take no time to execute.~~ ## References - [Log rules](https://www.rapidtables.com/math/algebra/Logarithm.html#log-rules) - [Rounding error playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6bd6c68b3539e400f9ca4fdc6fc2eed0) - [substack's tweet asking about integer log2 in the stdlib](https://twitter.com/substack/status/1236445105197727744) - [Integer Logarithm, A. Jaffer 2008](https://people.csail.mit.edu/jaffer/III/ilog.pdf)
This commit is contained in:
commit
9bbc470e97
@ -1744,6 +1744,194 @@ macro_rules! int_impl {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<Self> {
|
||||
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<Self> {
|
||||
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> {
|
||||
self.checked_log(10)
|
||||
}
|
||||
|
||||
/// Computes the absolute value of `self`.
|
||||
///
|
||||
/// # Overflow behavior
|
||||
|
@ -634,6 +634,194 @@ macro_rules! uint_impl {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<Self> {
|
||||
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<Self> {
|
||||
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> {
|
||||
self.checked_log(10)
|
||||
}
|
||||
|
||||
/// Checked negation. Computes `-self`, returning `None` unless `self ==
|
||||
/// 0`.
|
||||
///
|
||||
|
@ -44,6 +44,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)]
|
||||
|
99
library/core/tests/num/int_log.rs
Normal file
99
library/core/tests/num/int_log.rs
Normal file
@ -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));
|
||||
}
|
||||
}
|
@ -29,6 +29,7 @@ mod u8;
|
||||
mod bignum;
|
||||
mod dec2flt;
|
||||
mod flt2dec;
|
||||
mod int_log;
|
||||
mod ops;
|
||||
mod wrapping;
|
||||
|
||||
|
@ -278,6 +278,7 @@
|
||||
#![feature(hashmap_internals)]
|
||||
#![feature(int_error_internals)]
|
||||
#![feature(integer_atomics)]
|
||||
#![feature(int_log)]
|
||||
#![feature(into_future)]
|
||||
#![feature(intra_doc_pointers)]
|
||||
#![feature(iter_zip)]
|
||||
|
Loading…
Reference in New Issue
Block a user