add int_log10 and checked_int_log10

This commit is contained in:
Trevor Spiteri 2020-04-13 03:17:21 +02:00
parent 919519fea1
commit a32b56ab43
7 changed files with 515 additions and 37 deletions

View File

@ -76,15 +76,18 @@ The conversions supported cover the following cases.
### Version 0.5.5 news (unreleased)
* The associated constants [`MIN`] and [`MAX`] were added to all
fixed-point types, to the [`Fixed`] trait, and to the [`Wrapping`]
wrapper.
* The associated constants [`INT_NBITS`] and [`FRAC_NBITS`] were
added to the [`Fixed`] trait and to the [`Wrapping`] wrapper.
* The methods [`int_log2`] and [`checked_int_log2`] were added to
all fixed-point types and to the [`Fixed`] trait.
* The method [`int_log2`][wril] was added to the [`Wrapping`]
wrapper.
* The following associated constants were added to all fixed-point
types, to the [`Fixed`] trait, and to the [`Wrapping`] wrapper:
* [`MIN`], [`MAX`]
* The following associated constants were added to the [`Fixed`]
trait and to the [`Wrapping`] wrapper:
* [`INT_NBITS`], [`FRAC_NBITS`]
* The following methods were added to all fixed-point types and to
the [`Fixed`] trait:
* [`int_log2`], [`int_log10`]
* [`checked_int_log2`], [`checked_int_log10`]
* The following methods were added to the [`Wrapping`] wrapper:
* [`int_log2`][wril2], [`int_log10`][wril10]
* The following methods were deprecated:
* [`min_value`], [`max_value`]
* [`int_nbits`][`int_nbits()`], [`frac_nbits`][`frac_nbits()`]
@ -106,15 +109,15 @@ The conversions supported cover the following cases.
numbers.
* The following methods were added to all fixed-point types and to
the [`Fixed`] trait:
* [`checked_rem`]
* [`div_euclid`], [`rem_euclid`]
* [`checked_div_euclid`], [`checked_rem_euclid`]
* [`saturating_div_euclid`]
* [`wrapping_div_euclid`]
* [`checked_rem`]
* [`div_euclid`], [`rem_euclid`]
* [`checked_div_euclid`], [`checked_rem_euclid`]
* [`saturating_div_euclid`]
* [`wrapping_div_euclid`]
* [`overflowing_div_euclid`]
* The following methods were added to the [`Wrapping`] wrapper:
* [`div_euclid`][wde], [`rem_euclid`][wre]
* [`div_euclid_int`][wdei], [`rem_euclid_int`][wrei]
* [`div_euclid`][wde], [`rem_euclid`][wre]
* [`div_euclid_int`][wdei], [`rem_euclid_int`][wrei]
* The following methods were deprecated:
* [`wrapping_rem_int`], [`overflowing_rem_int`]
@ -131,11 +134,13 @@ The conversions supported cover the following cases.
[`Rem`]: https://doc.rust-lang.org/nightly/core/ops/trait.Rem.html
[`Wrapping`]: https://docs.rs/fixed/0.5.4/fixed/struct.Wrapping.html
[`checked_div_euclid`]: https://docs.rs/fixed/0.5.4/fixed/struct.FixedI32.html#method.checked_div_euclid
[`checked_int_log10`]: https://docs.rs/fixed/0.5.4/fixed/struct.FixedI32.html#method.checked_int_log10
[`checked_int_log2`]: https://docs.rs/fixed/0.5.4/fixed/struct.FixedI32.html#method.checked_int_log2
[`checked_rem_euclid`]: https://docs.rs/fixed/0.5.4/fixed/struct.FixedI32.html#method.checked_rem_euclid
[`checked_rem`]: https://docs.rs/fixed/0.5.4/fixed/struct.FixedI32.html#method.checked_rem
[`div_euclid`]: https://docs.rs/fixed/0.5.4/fixed/struct.FixedI32.html#method.div_euclid
[`frac_nbits()`]: https://docs.rs/fixed/0.5.4/fixed/struct.FixedI32.html#method.frac_nbits
[`int_log10`]: https://docs.rs/fixed/0.5.4/fixed/struct.FixedI32.html#method.int_log10
[`int_log2`]: https://docs.rs/fixed/0.5.4/fixed/struct.FixedI32.html#method.int_log2
[`int_nbits()`]: https://docs.rs/fixed/0.5.4/fixed/struct.FixedI32.html#method.int_nbits
[`max_value`]: https://docs.rs/fixed/0.5.4/fixed/struct.FixedI32.html#method.max_value
@ -153,7 +158,8 @@ The conversions supported cover the following cases.
[wdei]: https://docs.rs/fixed/0.5.4/fixed/struct.Wrapping.html#method.div_euclid_int
[wre]: https://docs.rs/fixed/0.5.4/fixed/struct.Wrapping.html#method.rem_euclid
[wrei]: https://docs.rs/fixed/0.5.4/fixed/struct.Wrapping.html#method.rem_euclid_int
[wril]: https://docs.rs/fixed/0.5.4/fixed/struct.Wrapping.html#method.int_log2
[wril10]: https://docs.rs/fixed/0.5.4/fixed/struct.Wrapping.html#method.int_log10
[wril2]: https://docs.rs/fixed/0.5.4/fixed/struct.Wrapping.html#method.int_log2
### Other releases

View File

@ -8,14 +8,18 @@ as-is, without any warranty. -->
Version 0.5.5 (unreleased)
==========================
* The associated constants `MIN` and `MAX` were added to all
fixed-point types, to the `Fixed` trait, and to the `Wrapping`
wrapper.
* The associated constants `INT_NBITS` and `FRAC_NBITS` were added
to the `Fixed` trait and to the `Wrapping` wrapper.
* The methods `int_log2` and `checked_int_log2` were added to all
fixed-point types and to the `Fixed` trait.
* The method `int_log2` was added to the `Wrapping` wrapper.
* The following associated constants were added to all fixed-point
types, to the `Fixed` trait, and to the `Wrapping` wrapper:
* `MIN`, `MAX`
* The following associated constants were added to the `Fixed` trait
and to the `Wrapping` wrapper:
* `INT_NBITS`, `FRAC_NBITS`
* The following methods were added to all fixed-point types and to
the `Fixed` trait:
* `int_log2`, `int_log10`
* `checked_int_log2`, `checked_int_log10`
* The following methods were added to the `Wrapping` wrapper:
* `int_log2`, `int_log10`
* The following methods were deprecated:
* `min_value`, `max_value`
* `int_nbits`, `frac_nbits`
@ -39,15 +43,15 @@ Version 0.5.3 (2020-02-13)
* `Rem` and `RemAssign` were implemented for fixed-point numbers.
* The following methods were added to all fixed-point types and to
the `Fixed` trait:
* `checked_rem`
* `div_euclid`, `rem_euclid`
* `checked_div_euclid`, `checked_rem_euclid`
* `saturating_div_euclid`
* `wrapping_div_euclid`
* `checked_rem`
* `div_euclid`, `rem_euclid`
* `checked_div_euclid`, `checked_rem_euclid`
* `saturating_div_euclid`
* `wrapping_div_euclid`
* `overflowing_div_euclid`
* The following methods were added to the `Wrapping` wrapper:
* `div_euclid`, `rem_euclid`
* `div_euclid_int`, `rem_euclid_int`
* `div_euclid`, `rem_euclid`
* `div_euclid_int`, `rem_euclid_int`
* The following methods were deprecated:
* `wrapping_rem_int`, `overflowing_rem_int`
@ -69,11 +73,11 @@ Version 0.5.0 (2019-12-06)
* The following methods were added to all fixed-point types and to
the `Fixed` trait:
* `from_be_bytes`, `from_le_bytes`, `from_ne_bytes`
* `to_be_bytes`, `to_le_bytes`, `to_ne_bytes`
* `div_euclid_int`, `rem_euclid_int`
* `checked_div_euclid_int`, `checked_rem_euclid_int`
* `wrapping_div_euclid_int`, `wrapping_rem_euclid_int`
* `overflowing_div_euclid_int`, `overflowing_rem_euclid_int`
* `to_be_bytes`, `to_le_bytes`, `to_ne_bytes`
* `div_euclid_int`, `rem_euclid_int`
* `checked_div_euclid_int`, `checked_rem_euclid_int`
* `wrapping_div_euclid_int`, `wrapping_rem_euclid_int`
* `overflowing_div_euclid_int`, `overflowing_rem_euclid_int`
Incompatible changes
--------------------

View File

@ -261,6 +261,7 @@ mod float_helper;
mod from_str;
mod helpers;
mod int_helper;
mod log10;
#[cfg(feature = "serde")]
mod serdeize;
pub mod traits;
@ -273,6 +274,7 @@ use crate::{
from_str::FromStrRadix,
traits::{FromFixed, ToFixed},
types::extra::{LeEqU128, LeEqU16, LeEqU32, LeEqU64, LeEqU8},
log10::IntFracLog10,
};
pub use crate::{from_str::ParseFixedError, wrapping::Wrapping};
use core::{

376
src/log10.rs Normal file
View File

@ -0,0 +1,376 @@
// Copyright © 20182019 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
// self must be positive
pub trait IntFracLog10 {
fn int_part_log10(self) -> i32;
fn frac_part_log10(self) -> i32;
}
impl IntFracLog10 for u8 {
#[inline]
fn int_part_log10(self) -> i32 {
if self >= 100 {
2
} else if self >= 10 {
1
} else {
debug_assert!(self >= 1);
0
}
}
#[inline]
fn frac_part_log10(self) -> i32 {
if self > 25 {
-1
} else if self > 2 {
-2
} else {
debug_assert!(self > 0);
-3
}
}
}
impl IntFracLog10 for u16 {
#[inline]
fn int_part_log10(self) -> i32 {
if self >= 10_000 {
4
} else if self >= 1000 {
3
} else if self >= 100 {
2
} else if self >= 10 {
1
} else {
debug_assert!(self >= 1);
0
}
}
#[inline]
fn frac_part_log10(self) -> i32 {
if self > 6553 {
-1
} else if self > 655 {
-2
} else if self > 65 {
-3
} else if self > 6 {
-4
} else {
debug_assert!(self > 0);
-5
}
}
}
#[inline]
fn int_part_log10_less_than_8(mut i: u32) -> i32 {
debug_assert!(i < 100_000_000);
let mut log = 0;
if i >= 10_000 {
i /= 10_000;
log += 4;
}
log + if i >= 1000 {
3
} else if i >= 100 {
2
} else if i >= 10 {
1
} else {
debug_assert!(i >= 1);
0
}
}
impl IntFracLog10 for u32 {
fn int_part_log10(mut self) -> i32 {
let mut log = 0;
if self >= 100_000_000 {
self /= 100_000_000;
log += 8;
}
log + int_part_log10_less_than_8(self)
}
fn frac_part_log10(mut self) -> i32 {
const MAX: u32 = u32::max_value();
let mut log = 0;
if self <= MAX / 100_000_000 {
self *= 100_000_000;
log += -8;
}
if self <= MAX / 10_000 {
self *= 10_000;
log += -4;
}
log + if self > MAX / 10 {
-1
} else if self > MAX / 100 {
-2
} else if self > MAX / 1000 {
-3
} else {
debug_assert!(self > MAX / 10_000);
-4
}
}
}
#[inline]
fn int_part_log10_less_than_16(mut i: u64) -> i32 {
debug_assert!(i < 10_000_000_000_000_000);
let mut log = 0;
if i >= 100_000_000 {
i /= 100_000_000;
log += 8;
}
debug_assert_eq!(i >> 32, 0);
log + int_part_log10_less_than_8(i as u32)
}
impl IntFracLog10 for u64 {
fn int_part_log10(mut self) -> i32 {
let mut log = 0;
if self >= 10_000_000_000_000_000 {
self /= 10_000_000_000_000_000;
log += 16;
}
log + int_part_log10_less_than_16(self)
}
fn frac_part_log10(mut self) -> i32 {
const MAX: u64 = u64::max_value();
let mut log = 0;
if self <= MAX / 10_000_000_000_000_000 {
log += -16;
self *= 10_000_000_000_000_000;
}
if self <= MAX / 100_000_000 {
log += -8;
self *= 100_000_000;
}
if self <= MAX / 10_000 {
log += -4;
self *= 10_000;
}
log + if self > MAX / 10 {
-1
} else if self > MAX / 100 {
-2
} else if self > MAX / 1000 {
-3
} else {
debug_assert!(self > MAX / 10_000);
-4
}
}
}
impl IntFracLog10 for u128 {
fn int_part_log10(mut self) -> i32 {
let mut log = 0;
if self >= 100_000_000_000_000_000_000_000_000_000_000 {
self /= 100_000_000_000_000_000_000_000_000_000_000;
log += 32;
}
if self >= 10_000_000_000_000_000 {
self /= 10_000_000_000_000_000;
log += 16;
}
debug_assert_eq!(self >> 64, 0);
log + int_part_log10_less_than_16(self as u64)
}
fn frac_part_log10(mut self) -> i32 {
const MAX: u128 = u128::max_value();
let mut log = 0;
if self <= MAX / 100_000_000_000_000_000_000_000_000_000_000 {
log += -32;
self *= 100_000_000_000_000_000_000_000_000_000_000;
}
if self <= MAX / 10_000_000_000_000_000 {
log += -16;
self *= 10_000_000_000_000_000;
}
if self <= MAX / 100_000_000 {
log += -8;
self *= 100_000_000;
}
if self <= MAX / 10_000 {
log += -4;
self *= 10_000;
}
log + if self > MAX / 10 {
-1
} else if self > MAX / 100 {
-2
} else if self > MAX / 1000 {
-3
} else {
debug_assert!(self > MAX / 10_000);
-4
}
}
}
#[cfg(test)]
mod tests {
use super::IntFracLog10;
#[test]
fn int_part_log10_u8() {
assert_eq!(u8::max_value().int_part_log10(), 2);
for i in 0..=2 {
let p = 10u8.pow(i as u32);
if i > 0 {
assert_eq!((p - 1).int_part_log10(), i - 1);
}
assert_eq!(p.int_part_log10(), i);
assert_eq!((p + 1).int_part_log10(), i);
}
}
#[test]
fn frac_part_log10_u8() {
assert_eq!(1u8.frac_part_log10(), -3);
for i in 0..=2 {
let p = u8::max_value() / 10u8.pow(i as u32);
if p > 1 {
assert_eq!((p - 1).frac_part_log10(), -1 - i);
}
assert_eq!(p.frac_part_log10(), -1 - i);
if i > 0 {
assert_eq!((p + 1).frac_part_log10(), -i);
}
}
}
#[test]
fn int_part_log10_u16() {
assert_eq!(u16::max_value().int_part_log10(), 4);
for i in 0..=4 {
let p = 10u16.pow(i as u32);
if i > 0 {
assert_eq!((p - 1).int_part_log10(), i - 1);
}
assert_eq!(p.int_part_log10(), i);
assert_eq!((p + 1).int_part_log10(), i);
}
}
#[test]
fn frac_part_log10_u16() {
assert_eq!(1u16.frac_part_log10(), -5);
for i in 0..=4 {
let p = u16::max_value() / 10u16.pow(i as u32);
if p > 1 {
assert_eq!((p - 1).frac_part_log10(), -1 - i);
}
assert_eq!(p.frac_part_log10(), -1 - i);
if i > 0 {
assert_eq!((p + 1).frac_part_log10(), -i);
}
}
}
#[test]
fn int_part_log10_u32() {
assert_eq!(u32::max_value().int_part_log10(), 9);
for i in 0..=9 {
let p = 10u32.pow(i as u32);
if i > 0 {
assert_eq!((p - 1).int_part_log10(), i - 1);
}
assert_eq!(p.int_part_log10(), i);
assert_eq!((p + 1).int_part_log10(), i);
}
}
#[test]
fn frac_part_log10_u32() {
assert_eq!(1u32.frac_part_log10(), -10);
for i in 0..=9 {
let p = u32::max_value() / 10u32.pow(i as u32);
if p > 1 {
assert_eq!((p - 1).frac_part_log10(), -1 - i);
}
assert_eq!(p.frac_part_log10(), -1 - i);
if i > 0 {
assert_eq!((p + 1).frac_part_log10(), -i);
}
}
}
#[test]
fn int_part_log10_u64() {
assert_eq!(u64::max_value().int_part_log10(), 19);
for i in 0..=19 {
let p = 10u64.pow(i as u32);
if i > 0 {
assert_eq!((p - 1).int_part_log10(), i - 1);
}
assert_eq!(p.int_part_log10(), i);
assert_eq!((p + 1).int_part_log10(), i);
}
}
#[test]
fn frac_part_log10_u64() {
assert_eq!(1u64.frac_part_log10(), -20);
for i in 0..=19 {
let p = u64::max_value() / 10u64.pow(i as u32);
if p > 1 {
assert_eq!((p - 1).frac_part_log10(), -1 - i);
}
assert_eq!(p.frac_part_log10(), -1 - i);
if i > 0 {
assert_eq!((p + 1).frac_part_log10(), -i);
}
}
}
#[test]
fn int_part_log10_u128() {
assert_eq!(u128::max_value().int_part_log10(), 38);
for i in 0..=38 {
let p = 10u128.pow(i as u32);
if i > 0 {
assert_eq!((p - 1).int_part_log10(), i - 1);
}
assert_eq!(p.int_part_log10(), i);
assert_eq!((p + 1).int_part_log10(), i);
}
}
#[test]
fn frac_part_log10_u128() {
assert_eq!(1u128.frac_part_log10(), -39);
for i in 0..=38 {
let p = u128::max_value() / 10u128.pow(i as u32);
if p > 1 {
assert_eq!((p - 1).frac_part_log10(), -1 - i);
}
assert_eq!(p.frac_part_log10(), -1 - i);
if i > 0 {
assert_eq!((p + 1).frac_part_log10(), -i);
}
}
}
}

View File

@ -90,6 +90,32 @@ assert_eq!(Fix::from_num(0.1875).int_log2(), -3);
}
}
comment! {
"Integer base-10 logarithm, rounded down.
# Panics
Panics if the fixed-point number is ", if_signed_unsigned!($Signedness, " 0", "zero"), ".
# Examples
```rust
use fixed::{
types::extra::{U2, U6},
", $s_fixed, ",
};
assert_eq!(", $s_fixed, "::<U2>::from_num(10).int_log10(), 1);
assert_eq!(", $s_fixed, "::<U2>::from_num(9.75).int_log10(), 0);
assert_eq!(", $s_fixed, "::<U6>::from_num(0.109375).int_log10(), -1);
assert_eq!(", $s_fixed, "::<U6>::from_num(0.09375).int_log10(), -2);
```
";
#[inline]
pub fn int_log10(self) -> i32 {
self.checked_int_log10().expect("log of non-positive number")
}
}
comment! {
"Checked integer base-2 logarithm, rounded down.
Returns the logarithm or [`None`] if the fixed-point number is
@ -119,6 +145,45 @@ assert_eq!(Fix::from_num(0.1875).checked_int_log2(), Some(-3));
}
}
// TODO when rustc requirement >= 1.43.0, use MAX instead of max_value()
comment! {
"Checked integer base-10 logarithm, rounded down.
Returns the logarithm or [`None`] if the fixed-point number is
", if_signed_unsigned!($Signedness, " 0", "zero"), ".
# Examples
```rust
use fixed::{
types::extra::{U2, U6},
", $s_fixed, ",
};
assert_eq!(", $s_fixed, "::<U2>::from_num(0).checked_int_log10(), None);
assert_eq!(", $s_fixed, "::<U2>::from_num(10).checked_int_log10(), Some(1));
assert_eq!(", $s_fixed, "::<U2>::from_num(9.75).checked_int_log10(), Some(0));
assert_eq!(", $s_fixed, "::<U6>::from_num(0.109375).checked_int_log10(), Some(-1));
assert_eq!(", $s_fixed, "::<U6>::from_num(0.09375).checked_int_log10(), Some(-2));
```
[`None`]: https://doc.rust-lang.org/nightly/core/option/enum.Option.html#variant.None
";
#[inline]
pub fn checked_int_log10(self) -> Option<i32> {
if self <= 0 {
return None;
}
// Use unsigned representation because we use all bits in fractional part.
let bits = self.to_bits() as $UInner;
let int = bits >> Self::FRAC_NBITS;
if int != 0 {
Some(int.int_part_log10())
} else {
let frac = bits << Self::INT_NBITS;
Some(frac.frac_part_log10())
}
}
}
if_signed! {
$Signedness;
comment! {

View File

@ -621,12 +621,25 @@ where
/// Panics if the fixed-point number is ≤ 0.
fn int_log2(self) -> i32;
/// Integer base-10 logarithm, rounded down.
///
/// # Panics
///
/// Panics if the fixed-point number is ≤ 0.
fn int_log10(self) -> i32;
/// Checked integer base-2 logarithm, rounded down. Returns the
/// logarithm or [`None`] if the fixed-point number is ≤ 0.
///
/// [`None`]: https://doc.rust-lang.org/nightly/core/option/enum.Option.html#variant.None
fn checked_int_log2(self) -> Option<i32>;
/// Checked integer base-10 logarithm, rounded down. Returns the
/// logarithm or [`None`] if the fixed-point number is ≤ 0.
///
/// [`None`]: https://doc.rust-lang.org/nightly/core/option/enum.Option.html#variant.None
fn checked_int_log10(self) -> Option<i32>;
/// Shifts to the left by `n` bits, wrapping the truncated bits to the right end.
fn rotate_left(self, n: u32) -> Self;
@ -1867,7 +1880,9 @@ macro_rules! impl_fixed {
trait_delegate! { fn leading_zeros(self) -> u32 }
trait_delegate! { fn trailing_zeros(self) -> u32 }
trait_delegate! { fn int_log2(self) -> i32 }
trait_delegate! { fn int_log10(self) -> i32 }
trait_delegate! { fn checked_int_log2(self) -> Option<i32> }
trait_delegate! { fn checked_int_log10(self) -> Option<i32> }
trait_delegate! { fn rotate_left(self, n: u32) -> Self }
trait_delegate! { fn rotate_right(self, n: u32) -> Self }
trait_delegate! { fn div_euclid(self, rhs: Self) -> Self }

View File

@ -501,6 +501,16 @@ impl<F: Fixed> Wrapping<F> {
self.0.int_log2()
}
/// Integer base-10 logarithm, rounded down.
///
/// # Panics
///
/// Panics if the fixed-point number is ≤ 0.
#[inline]
pub fn int_log10(self) -> i32 {
self.0.int_log10()
}
/// Shifts to the left by `n` bits, wrapping the truncated bits to the right end.
///
/// # Examples