forked from rust-lang/rust
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rollup merge of rust-lang#70835 - yoshuawuyts:int-log2, r=dtolnay
Add Integer::checked_{log,log2,log10} 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)
- Loading branch information
Showing
4 changed files
with
284 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
#[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)); | ||
|
||
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); | ||
|
||
for i in 1..=u8::MAX { | ||
assert_eq!(i.checked_log2(), Some((i as f32).log2() as u8)); | ||
} | ||
for i in 1..=u16::MAX { | ||
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 { | ||
assert_eq!(i.checked_log2(), Some((i as f32).log2() as i16)); | ||
} | ||
} | ||
|
||
#[test] | ||
fn checked_log10() { | ||
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)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters