Skip to content

Commit b5c1cb3

Browse files
authored
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)
2 parents 5db778a + 6cef103 commit b5c1cb3

File tree

4 files changed

+284
-0
lines changed

4 files changed

+284
-0
lines changed

src/libcore/num/mod.rs

+218
Original file line numberDiff line numberDiff line change
@@ -2160,6 +2160,115 @@ assert_eq!((-a).rem_euclid(-b), 1);
21602160
}
21612161
}
21622162

2163+
doc_comment! {
2164+
concat!("Returns the logarithm of the number with respect to an arbitrary base.
2165+
2166+
Returns `None` if the number is negative or zero, or if the base is not at least 2.
2167+
2168+
This method may not be optimized owing to implementation details;
2169+
`self.checked_log2()` can produce results more efficiently for base 2, and
2170+
`self.checked_log10()` can produce results more efficiently for base 10.
2171+
2172+
# Examples
2173+
2174+
```
2175+
#![feature(int_log)]
2176+
2177+
let five = 5", stringify!($SelfT), ";
2178+
2179+
// log5(5) == 1
2180+
let result = five.checked_log(5);
2181+
2182+
assert_eq!(result, Some(1));
2183+
```"),
2184+
#[unstable(feature = "int_log", issue = "70887")]
2185+
#[must_use = "this returns the result of the operation, \
2186+
without modifying the original"]
2187+
#[inline]
2188+
pub fn checked_log(self, base: Self) -> Option<Self> {
2189+
// SAFETY: We check the input to this is always positive
2190+
let logb2 = |x: Self| unsafe { intrinsics::ctlz_nonzero(1 as Self) - intrinsics::ctlz_nonzero(x) };
2191+
2192+
if self <= 0 || base <= 1 {
2193+
None
2194+
} else {
2195+
let mut n = 0;
2196+
let mut r = self;
2197+
2198+
// Optimization for 128 bit wide integers.
2199+
if mem::size_of::<Self>() * 8 == 128 {
2200+
let b = logb2(self) / (logb2(base) + 1);
2201+
n += b;
2202+
r /= base.pow(b as u32);
2203+
}
2204+
2205+
while r >= base {
2206+
r /= base;
2207+
n += 1;
2208+
}
2209+
Some(n)
2210+
}
2211+
}
2212+
}
2213+
2214+
doc_comment! {
2215+
concat!("Returns the base 2 logarithm of the number.
2216+
2217+
Returns `None` if the number is negative or zero.
2218+
2219+
# Examples
2220+
2221+
```
2222+
#![feature(int_log)]
2223+
2224+
let two = 2", stringify!($SelfT), ";
2225+
2226+
// checked_log2(2) == 1
2227+
let result = two.checked_log2();
2228+
2229+
assert_eq!(result, Some(1));
2230+
```"),
2231+
#[unstable(feature = "int_log", issue = "70887")]
2232+
#[must_use = "this returns the result of the operation, \
2233+
without modifying the original"]
2234+
#[inline]
2235+
pub fn checked_log2(self) -> Option<Self> {
2236+
if self <= 0 {
2237+
None
2238+
} else {
2239+
// SAFETY: We just checked that this number is positive
2240+
let log = unsafe { intrinsics::ctlz_nonzero(1 as Self) - intrinsics::ctlz_nonzero(self) };
2241+
Some(log)
2242+
}
2243+
}
2244+
}
2245+
2246+
doc_comment! {
2247+
concat!("Returns the base 10 logarithm of the number.
2248+
2249+
Returns `None` if the number is negative or zero.
2250+
2251+
# Examples
2252+
2253+
```
2254+
#![feature(int_log)]
2255+
2256+
let ten = 10", stringify!($SelfT), ";
2257+
2258+
// checked_log10(10) == 1
2259+
let result = ten.checked_log10();
2260+
2261+
assert_eq!(result, Some(1));
2262+
```"),
2263+
#[unstable(feature = "int_log", issue = "70887")]
2264+
#[must_use = "this returns the result of the operation, \
2265+
without modifying the original"]
2266+
#[inline]
2267+
pub fn checked_log10(self) -> Option<Self> {
2268+
self.checked_log(10)
2269+
}
2270+
}
2271+
21632272
doc_comment! {
21642273
concat!("Computes the absolute value of `self`.
21652274
@@ -4169,6 +4278,115 @@ assert_eq!(7", stringify!($SelfT), ".rem_euclid(4), 3); // or any other integer
41694278
}
41704279
}
41714280

4281+
doc_comment! {
4282+
concat!("Returns the logarithm of the number with respect to an arbitrary base.
4283+
4284+
Returns `None` if the number is zero, or if the base is not at least 2.
4285+
4286+
This method may not be optimized owing to implementation details;
4287+
`self.checked_log2()` can produce results more efficiently for base 2, and
4288+
`self.checked_log10()` can produce results more efficiently for base 10.
4289+
4290+
# Examples
4291+
4292+
```
4293+
#![feature(int_log)]
4294+
4295+
let five = 5", stringify!($SelfT), ";
4296+
4297+
// log5(5) == 1
4298+
let result = five.checked_log(5);
4299+
4300+
assert_eq!(result, Some(1));
4301+
```"),
4302+
#[unstable(feature = "int_log", issue = "70887")]
4303+
#[must_use = "this returns the result of the operation, \
4304+
without modifying the original"]
4305+
#[inline]
4306+
pub fn checked_log(self, base: Self) -> Option<Self> {
4307+
// SAFETY: We check the input to this is always positive.
4308+
let logb2 = |x: Self| unsafe { intrinsics::ctlz_nonzero(1 as Self) - intrinsics::ctlz_nonzero(x) };
4309+
4310+
if self <= 0 || base <= 1 {
4311+
None
4312+
} else {
4313+
let mut n = 0;
4314+
let mut r = self;
4315+
4316+
// Optimization for 128 bit wide integers.
4317+
if mem::size_of::<Self>() * 8 == 128 {
4318+
let b = logb2(self) / (logb2(base) + 1);
4319+
n += b;
4320+
r /= base.pow(b as u32);
4321+
}
4322+
4323+
while r >= base {
4324+
r /= base;
4325+
n += 1;
4326+
}
4327+
Some(n)
4328+
}
4329+
}
4330+
}
4331+
4332+
doc_comment! {
4333+
concat!("Returns the base 2 logarithm of the number.
4334+
4335+
Returns `None` if the number is zero.
4336+
4337+
# Examples
4338+
4339+
```
4340+
#![feature(int_log)]
4341+
4342+
let two = 2", stringify!($SelfT), ";
4343+
4344+
// checked_log2(2) == 1
4345+
let result = two.checked_log2();
4346+
4347+
assert_eq!(result, Some(1));
4348+
```"),
4349+
#[unstable(feature = "int_log", issue = "70887")]
4350+
#[must_use = "this returns the result of the operation, \
4351+
without modifying the original"]
4352+
#[inline]
4353+
pub fn checked_log2(self) -> Option<Self> {
4354+
if self <= 0 {
4355+
None
4356+
} else {
4357+
// SAFETY: We just checked that this number is positive
4358+
let log = unsafe { intrinsics::ctlz_nonzero(1 as Self) - intrinsics::ctlz_nonzero(self) };
4359+
Some(log)
4360+
}
4361+
}
4362+
}
4363+
4364+
doc_comment! {
4365+
concat!("Returns the base 10 logarithm of the number.
4366+
4367+
Returns `None` if the number is zero.
4368+
4369+
# Examples
4370+
4371+
```
4372+
#![feature(int_log)]
4373+
4374+
let ten = 10", stringify!($SelfT), ";
4375+
4376+
// checked_log10(10) == 1
4377+
let result = ten.checked_log10();
4378+
4379+
assert_eq!(result, Some(1));
4380+
```"),
4381+
#[unstable(feature = "int_log", issue = "70887")]
4382+
#[must_use = "this returns the result of the operation, \
4383+
without modifying the original"]
4384+
#[inline]
4385+
pub fn checked_log10(self) -> Option<Self> {
4386+
self.checked_log(10)
4387+
}
4388+
}
4389+
41724390
doc_comment! {
41734391
concat!("Returns `true` if and only if `self == 2^k` for some `k`.
41744392

src/libcore/tests/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#![feature(fmt_internals)]
1414
#![feature(hashmap_internals)]
1515
#![feature(try_find)]
16+
#![feature(int_log)]
1617
#![feature(is_sorted)]
1718
#![feature(pattern)]
1819
#![feature(range_is_empty)]

src/libcore/tests/num/int_log.rs

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#[test]
2+
fn checked_log() {
3+
assert_eq!(999u32.checked_log(10), Some(2));
4+
assert_eq!(1000u32.checked_log(10), Some(3));
5+
assert_eq!(555u32.checked_log(13), Some(2));
6+
assert_eq!(63u32.checked_log(4), Some(2));
7+
assert_eq!(64u32.checked_log(4), Some(3));
8+
assert_eq!(10460353203u64.checked_log(3), Some(21));
9+
assert_eq!(10460353202u64.checked_log(3), Some(20));
10+
assert_eq!(147808829414345923316083210206383297601u128.checked_log(3), Some(80));
11+
assert_eq!(147808829414345923316083210206383297600u128.checked_log(3), Some(79));
12+
assert_eq!(22528399544939174411840147874772641u128.checked_log(19683), Some(8));
13+
assert_eq!(22528399544939174411840147874772631i128.checked_log(19683), Some(7));
14+
15+
for i in i16::MIN..=0 {
16+
assert_eq!(i.checked_log(4), None);
17+
}
18+
for i in 1..=i16::MAX {
19+
assert_eq!(i.checked_log(13), Some((i as f32).log(13.0) as i16));
20+
}
21+
for i in 1..=u16::MAX {
22+
assert_eq!(i.checked_log(13), Some((i as f32).log(13.0) as u16));
23+
}
24+
}
25+
26+
#[test]
27+
fn checked_log2() {
28+
assert_eq!(5u32.checked_log2(), Some(2));
29+
assert_eq!(0u64.checked_log2(), None);
30+
assert_eq!(128i32.checked_log2(), Some(7));
31+
assert_eq!((-55i16).checked_log2(), None);
32+
33+
for i in 1..=u8::MAX {
34+
assert_eq!(i.checked_log2(), Some((i as f32).log2() as u8));
35+
}
36+
for i in 1..=u16::MAX {
37+
assert_eq!(i.checked_log2(), Some((i as f32).log2() as u16));
38+
}
39+
for i in i8::MIN..=0 {
40+
assert_eq!(i.checked_log2(), None);
41+
}
42+
for i in 1..=i8::MAX {
43+
assert_eq!(i.checked_log2(), Some((i as f32).log2() as i8));
44+
}
45+
for i in i16::MIN..=0 {
46+
assert_eq!(i.checked_log2(), None);
47+
}
48+
for i in 1..=i16::MAX {
49+
assert_eq!(i.checked_log2(), Some((i as f32).log2() as i16));
50+
}
51+
}
52+
53+
#[test]
54+
fn checked_log10() {
55+
for i in i16::MIN..=0 {
56+
assert_eq!(i.checked_log10(), None);
57+
}
58+
for i in 1..=i16::MAX {
59+
assert_eq!(i.checked_log10(), Some((i as f32).log10() as i16));
60+
}
61+
for i in 1..=u16::MAX {
62+
assert_eq!(i.checked_log10(), Some((i as f32).log10() as u16));
63+
}
64+
}

src/libcore/tests/num/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ mod u8;
2626
mod bignum;
2727
mod dec2flt;
2828
mod flt2dec;
29+
mod int_log;
2930

3031
/// Adds the attribute to all items in the block.
3132
macro_rules! cfg_block {

0 commit comments

Comments
 (0)