fixed/
log.rs

1// Copyright © 2018–2025 Trevor Spiteri
2
3// This library is free software: you can redistribute it and/or
4// modify it under the terms of either
5//
6//   * the Apache License, Version 2.0 or
7//   * the MIT License
8//
9// at your option.
10//
11// You should have recieved copies of the Apache License and the MIT
12// License along with the library. If not, see
13// <https://www.apache.org/licenses/LICENSE-2.0> and
14// <https://opensource.org/licenses/MIT>.
15
16#[derive(Clone, Copy, Debug)]
17pub struct Base(u32);
18
19impl Base {
20    pub const fn new(base: u32) -> Option<Base> {
21        if base >= 2 { Some(Base(base)) } else { None }
22    }
23
24    pub const fn get(self) -> u32 {
25        self.0
26    }
27}
28
29macro_rules! impl_int_part {
30    ($u:ident) => {
31        pub const fn $u(val: NonZero<$u>, base: Base) -> i32 {
32            const MAX_TABLE_SIZE: usize = ($u::BITS.ilog2() - 1) as usize;
33
34            let val = val.get();
35            let base = base.get();
36
37            let baseu = base as $u;
38            if baseu as u32 != base || val < baseu {
39                return 0;
40            }
41
42            // base^1, base^2, base^4, etc.
43            let mut base_powers: [$u; MAX_TABLE_SIZE] = [0; MAX_TABLE_SIZE];
44
45            let mut i = 0;
46            let mut partial_log = 1u32;
47            let mut partial_val = baseu;
48
49            loop {
50                let square = match partial_val.checked_mul(partial_val) {
51                    Some(s) if val >= s => s,
52                    _ => break,
53                };
54                base_powers[i] = partial_val;
55                i += 1;
56                partial_log *= 2;
57                partial_val = square;
58            }
59            let mut dlog = partial_log;
60            while i > 0 {
61                i -= 1;
62                dlog /= 2;
63                if let Some(mid) = partial_val.checked_mul(base_powers[i]) {
64                    if val >= mid {
65                        partial_val = mid;
66                        partial_log += dlog;
67                    }
68                }
69            }
70            return partial_log as i32;
71        }
72    };
73}
74
75pub mod int_part {
76    use crate::log::Base;
77    use core::num::NonZero;
78
79    impl_int_part! { u8 }
80    impl_int_part! { u16 }
81    impl_int_part! { u32 }
82    impl_int_part! { u64 }
83    impl_int_part! { u128 }
84}
85
86macro_rules! impl_frac_part {
87    ($u:ident) => {
88        pub const fn $u(val: NonZero<$u>, base: Base) -> i32 {
89            const MAX_TABLE_SIZE: usize = ($u::BITS.ilog2() - 1) as usize;
90
91            let val = val.get();
92            let base = base.get();
93
94            let baseu = base as $u;
95            if baseu as u32 != base || val.checked_mul(baseu).is_none() {
96                return -1;
97            }
98
99            // base^1, base^2, base^4, etc.
100            let mut base_powers: [$u; MAX_TABLE_SIZE] = [0; MAX_TABLE_SIZE];
101
102            let mut i = 0;
103            let mut partial_log = 1u32;
104            let mut partial_val = baseu;
105
106            loop {
107                let square = match partial_val.checked_mul(partial_val) {
108                    Some(s) if val.checked_mul(s).is_some() => s,
109                    _ => break,
110                };
111                base_powers[i] = partial_val;
112                i += 1;
113                partial_log *= 2;
114                partial_val = square;
115            }
116            let mut dlog = partial_log;
117            while i > 0 {
118                i -= 1;
119                dlog /= 2;
120                if let Some(mid) = partial_val.checked_mul(base_powers[i]) {
121                    if val.checked_mul(mid).is_some() {
122                        partial_val = mid;
123                        partial_log += dlog;
124                    }
125                }
126            }
127            return -1 - partial_log as i32;
128        }
129    };
130}
131
132pub mod frac_part {
133    use crate::log::Base;
134    use core::num::NonZero;
135
136    impl_frac_part! { u8 }
137    impl_frac_part! { u16 }
138    impl_frac_part! { u32 }
139    impl_frac_part! { u64 }
140    impl_frac_part! { u128 }
141}
142
143#[cfg(test)]
144mod tests {
145    use crate::log;
146    use crate::log::Base;
147    use core::num::NonZero;
148
149    // these tests require the maximum table sizes
150    #[test]
151    fn check_table_size_is_sufficient() {
152        let bin = Base::new(2).unwrap();
153
154        assert_eq!(log::int_part::u8(NonZero::<u8>::MAX, bin), 7);
155        assert_eq!(log::int_part::u16(NonZero::<u16>::MAX, bin), 15);
156        assert_eq!(log::int_part::u32(NonZero::<u32>::MAX, bin), 31);
157        assert_eq!(log::int_part::u64(NonZero::<u64>::MAX, bin), 63);
158        assert_eq!(log::int_part::u128(NonZero::<u128>::MAX, bin), 127);
159
160        assert_eq!(log::frac_part::u8(NonZero::<u8>::new(1).unwrap(), bin), -8);
161        assert_eq!(
162            log::frac_part::u16(NonZero::<u16>::new(1).unwrap(), bin),
163            -16
164        );
165        assert_eq!(
166            log::frac_part::u32(NonZero::<u32>::new(1).unwrap(), bin),
167            -32
168        );
169        assert_eq!(
170            log::frac_part::u64(NonZero::<u64>::new(1).unwrap(), bin),
171            -64
172        );
173        assert_eq!(
174            log::frac_part::u128(NonZero::<u128>::new(1).unwrap(), bin),
175            -128
176        );
177    }
178}