use crate::Uint;
impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
#[must_use]
pub fn checked_log(self, base: Self) -> Option<usize> {
if base < Self::from(2) || self == Self::ZERO {
return None;
}
Some(self.log(base))
}
#[must_use]
pub fn checked_log10(self) -> Option<usize> {
self.checked_log(Self::from(10))
}
#[must_use]
pub fn checked_log2(self) -> Option<usize> {
self.checked_log(Self::from(2))
}
#[must_use]
pub fn log(self, base: Self) -> usize {
assert!(self != Self::ZERO);
assert!(base >= Self::from(2));
if base == Self::from(2) {
return self.bit_len() - 1;
}
if self < base {
return 0;
}
#[allow(clippy::cast_precision_loss)] let result = self.approx_log2() / base.approx_log2();
assert!(result.is_normal());
let mut result = result.try_into().unwrap();
loop {
if let Some(value) = base.checked_pow(result) {
if value > self {
assert!(result != Self::ZERO);
result -= Self::from(1);
continue;
}
} else {
result -= Self::from(1);
}
break;
}
while let Some(trial) = result.checked_add(Self::from(1)) {
if let Some(value) = base.checked_pow(trial) {
if value <= self {
result = trial;
continue;
}
}
break;
}
result.to()
}
#[must_use]
pub fn log10(self) -> usize {
self.log(Self::from(10))
}
#[must_use]
pub fn log2(self) -> usize {
self.log(Self::from(2))
}
#[must_use]
pub fn approx_log(self, base: f64) -> f64 {
self.approx_log2() / base.log2()
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn approx_log2(self) -> f64 {
let (bits, exp) = self.most_significant_bits();
let bits = bits as f64;
let exp = exp as f64;
bits.log2() + exp
}
#[must_use]
pub fn approx_log10(self) -> f64 {
self.approx_log2() / core::f64::consts::LOG2_10
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{aliases::U128, const_for, nlimbs};
use proptest::{prop_assume, proptest};
#[test]
fn test_checked_log2() {
assert_eq!(U128::from(0).checked_log2(), None);
assert_eq!(U128::from(1).checked_log2(), Some(0));
assert_eq!(U128::from(2).checked_log2(), Some(1));
assert_eq!(U128::from(3).checked_log2(), Some(1));
assert_eq!(U128::from(127).checked_log2(), Some(6));
assert_eq!(U128::from(128).checked_log2(), Some(7));
}
#[test]
fn test_approx_log2_pow2() {
const_for!(BITS in SIZES {
const LIMBS: usize = nlimbs(BITS);
type U = Uint<BITS, LIMBS>;
proptest!(|(value: U)| {
let log = value.approx_log2();
let pow = U::approx_pow2(log).unwrap();
let error = value.abs_diff(pow);
let correct_bits = value.bit_len() - error.bit_len();
assert!(correct_bits == value.bit_len() || correct_bits >= 42);
});
});
}
#[test]
fn test_pow_log() {
const_for!(BITS in NON_ZERO if (BITS >= 64) {
const LIMBS: usize = nlimbs(BITS);
type U = Uint<BITS, LIMBS>;
proptest!(|(b in 2_u64..100, e in 0..BITS)| {
if let Some(value) = U::from(b).checked_pow(U::from(e)) {
assert!(value > U::ZERO);
assert_eq!(value.log(U::from(b)), e);
}
});
});
}
#[test]
fn test_log_pow() {
const_for!(BITS in NON_ZERO if (BITS >= 64) {
const LIMBS: usize = nlimbs(BITS);
type U = Uint<BITS, LIMBS>;
proptest!(|(b in 2_u64..100, n: U)| {
prop_assume!(n > U::ZERO);
let e = n.log(U::from(b));
assert!(U::from(b).pow(U::from(e)) <= n);
if let Some(value) = U::from(b).checked_pow(U::from(e + 1)) {
assert!(value > n);
}
});
});
}
}
#[cfg(feature = "bench")]
#[doc(hidden)]
pub mod bench {
use super::*;
use crate::{const_for, nlimbs};
use ::proptest::{
arbitrary::Arbitrary,
strategy::{Strategy, ValueTree},
test_runner::TestRunner,
};
use criterion::{black_box, BatchSize, Criterion};
pub fn group(criterion: &mut Criterion) {
const_for!(BITS in BENCH {
const LIMBS: usize = nlimbs(BITS);
bench_log::<BITS, LIMBS>(criterion);
});
}
fn bench_log<const BITS: usize, const LIMBS: usize>(criterion: &mut Criterion) {
if BITS < 7 {
return;
}
let input = (Uint::<BITS, LIMBS>::arbitrary(), 2_u64..100);
let mut runner = TestRunner::deterministic();
criterion.bench_function(&format!("log/{BITS}"), move |bencher| {
bencher.iter_batched(
|| input.new_tree(&mut runner).unwrap().current(),
|(n, b)| black_box(black_box(n).checked_log(Uint::<BITS, LIMBS>::from(b))),
BatchSize::SmallInput,
);
});
}
}