diff --git a/README.md b/README.md index c5fd59b..510a3e5 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/RELEASES.md b/RELEASES.md index 49c39f0..b752f31 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -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 -------------------- diff --git a/src/lib.rs b/src/lib.rs index 9501163..f679dac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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::{ diff --git a/src/log10.rs b/src/log10.rs new file mode 100644 index 0000000..ab69712 --- /dev/null +++ b/src/log10.rs @@ -0,0 +1,376 @@ +// Copyright © 2018–2019 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 +// and +// . + +// 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); + } + } + } +} diff --git a/src/macros_frac.rs b/src/macros_frac.rs index b19cb29..74e74ab 100644 --- a/src/macros_frac.rs +++ b/src/macros_frac.rs @@ -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, "::::from_num(10).int_log10(), 1); +assert_eq!(", $s_fixed, "::::from_num(9.75).int_log10(), 0); +assert_eq!(", $s_fixed, "::::from_num(0.109375).int_log10(), -1); +assert_eq!(", $s_fixed, "::::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, "::::from_num(0).checked_int_log10(), None); +assert_eq!(", $s_fixed, "::::from_num(10).checked_int_log10(), Some(1)); +assert_eq!(", $s_fixed, "::::from_num(9.75).checked_int_log10(), Some(0)); +assert_eq!(", $s_fixed, "::::from_num(0.109375).checked_int_log10(), Some(-1)); +assert_eq!(", $s_fixed, "::::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 { + 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! { diff --git a/src/traits.rs b/src/traits.rs index ee3dffd..7a232ed 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -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; + /// 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; + /// 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 } + trait_delegate! { fn checked_int_log10(self) -> Option } 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 } diff --git a/src/wrapping.rs b/src/wrapping.rs index f933c08..e2673d3 100644 --- a/src/wrapping.rs +++ b/src/wrapping.rs @@ -501,6 +501,16 @@ impl Wrapping { 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