use super::tables::LOG2_TABLE;
#[inline(always)]
pub(crate) const fn log2_floor_non_zero(value: usize) -> u32 {
(usize::BITS - 1) - value.leading_zeros()
}
#[inline]
pub(crate) fn fast_log2(value: usize) -> f64 {
match LOG2_TABLE.get(value) {
Some(&entry) => entry,
None => log2_beyond_table(value),
}
}
#[cold]
#[inline(never)]
fn log2_beyond_table(value: usize) -> f64 {
#[cfg(not(feature = "no_std"))]
{
(value as f64).log2()
}
#[cfg(feature = "no_std")]
{
libm::log2(value as f64)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "no_std")]
#[test]
fn portable_logarithms_match_the_reference_within_one_ulp() {
for value in [257, 1023, 65535, 1 << 24, usize::MAX] {
let expected = (value as f64).log2();
assert!(fast_log2(value).to_bits().abs_diff(expected.to_bits()) <= 1);
}
}
#[test]
fn floor_log2_matches_the_bit_width() {
assert_eq!(log2_floor_non_zero(1), 0);
assert_eq!(log2_floor_non_zero(2), 1);
assert_eq!(log2_floor_non_zero(3), 1);
assert_eq!(log2_floor_non_zero(255), 7);
assert_eq!(log2_floor_non_zero(256), 8);
}
#[test]
fn fast_log2_reads_the_table_below_it_and_computes_above() {
assert_eq!(fast_log2(0), 0.0);
assert_eq!(fast_log2(1), 0.0);
assert_eq!(fast_log2(2), 1.0);
assert_eq!(fast_log2(255), LOG2_TABLE[255]);
assert_eq!(fast_log2(256), 8.0);
assert_eq!(fast_log2(1024), 10.0);
}
}