#[derive(Clone, Copy, Debug)]
pub struct Base(u32);
impl Base {
pub const fn new(base: u32) -> Option<Base> {
if base >= 2 { Some(Base(base)) } else { None }
}
pub const fn get(self) -> u32 {
self.0
}
}
macro_rules! impl_int_part {
($u:ident) => {
pub const fn $u(val: NonZero<$u>, base: Base) -> i32 {
const MAX_TABLE_SIZE: usize = ($u::BITS.ilog2() - 1) as usize;
let val = val.get();
let base = base.get();
let baseu = base as $u;
if baseu as u32 != base || val < baseu {
return 0;
}
let mut base_powers: [$u; MAX_TABLE_SIZE] = [0; MAX_TABLE_SIZE];
let mut i = 0;
let mut partial_log = 1u32;
let mut partial_val = baseu;
loop {
let square = match partial_val.checked_mul(partial_val) {
Some(s) if val >= s => s,
_ => break,
};
base_powers[i] = partial_val;
i += 1;
partial_log *= 2;
partial_val = square;
}
let mut dlog = partial_log;
while i > 0 {
i -= 1;
dlog /= 2;
if let Some(mid) = partial_val.checked_mul(base_powers[i]) {
if val >= mid {
partial_val = mid;
partial_log += dlog;
}
}
}
return partial_log as i32;
}
};
}
pub mod int_part {
use crate::log::Base;
use core::num::NonZero;
impl_int_part! { u8 }
impl_int_part! { u16 }
impl_int_part! { u32 }
impl_int_part! { u64 }
impl_int_part! { u128 }
}
macro_rules! impl_frac_part {
($u:ident) => {
pub const fn $u(val: NonZero<$u>, base: Base) -> i32 {
const MAX_TABLE_SIZE: usize = ($u::BITS.ilog2() - 1) as usize;
let val = val.get();
let base = base.get();
let baseu = base as $u;
if baseu as u32 != base || val.checked_mul(baseu).is_none() {
return -1;
}
let mut base_powers: [$u; MAX_TABLE_SIZE] = [0; MAX_TABLE_SIZE];
let mut i = 0;
let mut partial_log = 1u32;
let mut partial_val = baseu;
loop {
let square = match partial_val.checked_mul(partial_val) {
Some(s) if val.checked_mul(s).is_some() => s,
_ => break,
};
base_powers[i] = partial_val;
i += 1;
partial_log *= 2;
partial_val = square;
}
let mut dlog = partial_log;
while i > 0 {
i -= 1;
dlog /= 2;
if let Some(mid) = partial_val.checked_mul(base_powers[i]) {
if val.checked_mul(mid).is_some() {
partial_val = mid;
partial_log += dlog;
}
}
}
return -1 - partial_log as i32;
}
};
}
pub mod frac_part {
use crate::log::Base;
use core::num::NonZero;
impl_frac_part! { u8 }
impl_frac_part! { u16 }
impl_frac_part! { u32 }
impl_frac_part! { u64 }
impl_frac_part! { u128 }
}
#[cfg(test)]
mod tests {
use crate::log;
use crate::log::Base;
use core::num::NonZero;
#[test]
fn check_table_size_is_sufficient() {
let bin = Base::new(2).unwrap();
assert_eq!(log::int_part::u8(NonZero::<u8>::MAX, bin), 7);
assert_eq!(log::int_part::u16(NonZero::<u16>::MAX, bin), 15);
assert_eq!(log::int_part::u32(NonZero::<u32>::MAX, bin), 31);
assert_eq!(log::int_part::u64(NonZero::<u64>::MAX, bin), 63);
assert_eq!(log::int_part::u128(NonZero::<u128>::MAX, bin), 127);
assert_eq!(log::frac_part::u8(NonZero::<u8>::new(1).unwrap(), bin), -8);
assert_eq!(
log::frac_part::u16(NonZero::<u16>::new(1).unwrap(), bin),
-16
);
assert_eq!(
log::frac_part::u32(NonZero::<u32>::new(1).unwrap(), bin),
-32
);
assert_eq!(
log::frac_part::u64(NonZero::<u64>::new(1).unwrap(), bin),
-64
);
assert_eq!(
log::frac_part::u128(NonZero::<u128>::new(1).unwrap(), bin),
-128
);
}
}