Skip to main content

dashu_int/
log.rs

1//! Logarithm
2
3use crate::{ibig::IBig, ops::EstimatedLog2, ubig::UBig};
4
5impl UBig {
6    /// Calculate the (truncated) logarithm of the [UBig]
7    ///
8    /// This function could takes a long time when the integer is very large.
9    /// In applications where an exact result is not necessary,
10    /// [log2_bounds][UBig::log2_bounds] could be used.
11    ///
12    /// # Panics
13    ///
14    /// Panics if the number is 0, or the base is 0 or 1
15    ///
16    /// # Examples
17    ///
18    /// ```
19    /// # use dashu_int::UBig;
20    /// let base = UBig::from(3u8);
21    /// assert_eq!(UBig::from(81u8).ilog(&base), 4);
22    /// assert_eq!(UBig::from(1000u16).ilog(&base), 6);
23    /// ```
24    #[inline]
25    pub fn ilog(&self, base: &UBig) -> usize {
26        self.repr().log(base.repr()).0
27    }
28}
29
30impl EstimatedLog2 for UBig {
31    #[inline]
32    fn log2_bounds(&self) -> (f32, f32) {
33        self.repr().log2_bounds()
34    }
35}
36
37impl IBig {
38    /// Calculate the (truncated) logarithm of the magnitude of [IBig]
39    ///
40    /// This function could takes a long time when the integer is very large.
41    /// In applications where an exact result is not necessary,
42    /// [log2_bounds][IBig::log2_bounds] could be used.
43    ///
44    /// # Panics
45    ///
46    /// Panics if the number is 0, or the base is 0 or 1
47    ///
48    /// # Examples
49    ///
50    /// ```
51    /// # use dashu_int::{UBig, IBig};
52    /// let base = UBig::from(3u8);
53    /// assert_eq!(IBig::from(-81).ilog(&base), 4);
54    /// assert_eq!(IBig::from(-1000).ilog(&base), 6);
55    /// ```
56    #[inline]
57    pub fn ilog(&self, base: &UBig) -> usize {
58        self.as_sign_repr().1.log(base.repr()).0
59    }
60}
61
62impl EstimatedLog2 for IBig {
63    #[inline]
64    fn log2_bounds(&self) -> (f32, f32) {
65        self.as_sign_repr().1.log2_bounds()
66    }
67}
68
69pub(crate) mod repr {
70    use alloc::vec::Vec;
71    use core::cmp::Ordering;
72
73    use dashu_base::EstimatedLog2;
74
75    use crate::{
76        arch::word::{DoubleWord, Word},
77        buffer::Buffer,
78        cmp::cmp_in_place,
79        div,
80        error::panic_invalid_log_oprand,
81        helper_macros::debug_assert_zero,
82        math::{bit_len, max_exp_in_word},
83        memory::MemoryAllocation,
84        mul, mul_ops, pow,
85        primitive::{extend_word, highest_dword, shrink_dword, split_dword, WORD_BITS_USIZE},
86        radix,
87        repr::{
88            Repr,
89            TypedReprRef::{self, *},
90        },
91        shift,
92    };
93
94    impl TypedReprRef<'_> {
95        /// Floor logarithm, returns (log(self), base^log(self))
96        pub fn log(self, base: TypedReprRef<'_>) -> (usize, Repr) {
97            // shortcuts
98            if let RefSmall(dw) = base {
99                match dw {
100                    0 | 1 => panic_invalid_log_oprand(),
101                    2 => {
102                        return (
103                            self.bit_len() - 1,
104                            Repr::zero().into_typed().set_bit(self.bit_len()),
105                        )
106                    }
107                    b if b.is_power_of_two() => {
108                        let base_bits = b.trailing_zeros() as usize;
109                        let exp = (self.bit_len() - 1) / base_bits;
110                        return (exp, Repr::zero().into_typed().set_bit(exp * base_bits));
111                    }
112                    _ => {}
113                }
114            }
115
116            match (self, base) {
117                (RefSmall(dword), RefSmall(base_dword)) => log_dword(dword, base_dword),
118                (RefSmall(_), RefLarge(_)) => (0, Repr::one()),
119                (RefLarge(words), RefSmall(base_dword)) => {
120                    if let Some(base_word) = shrink_dword(base_dword) {
121                        log_word_base(words, base_word)
122                    } else {
123                        let mut buffer: [Word; 2] = [0; 2];
124                        let (lo, hi) = split_dword(base_dword);
125                        buffer[0] = lo;
126                        buffer[1] = hi;
127                        log_large(words, &buffer)
128                    }
129                }
130                (RefLarge(words), RefLarge(base_words)) => match cmp_in_place(words, base_words) {
131                    Ordering::Less => (0, Repr::one()),
132                    Ordering::Equal => (1, Repr::from_buffer(Buffer::from(words))),
133                    Ordering::Greater => log_large(words, base_words),
134                },
135            }
136        }
137
138        pub fn log2_bounds(self) -> (f32, f32) {
139            match self {
140                RefSmall(dword) => dword.log2_bounds(),
141                RefLarge(words) => log2_bounds_large(words),
142            }
143        }
144    }
145
146    fn log_dword(target: DoubleWord, base: DoubleWord) -> (usize, Repr) {
147        debug_assert!(base > 1);
148
149        // shortcuts
150        match target {
151            0 => panic_invalid_log_oprand(),
152            1 => return (0, Repr::one()),
153            i if i < base => return (0, Repr::one()),
154            i if i == base => return (1, Repr::from_dword(base)),
155            _ => {}
156        }
157
158        let log2_self = target.log2_bounds().0;
159        let log2_base = base.log2_bounds().1;
160
161        let mut est = (log2_self / log2_base) as u32; // float to int is underestimate
162        let mut est_pow = base.pow(est);
163        assert!(est_pow <= target);
164
165        while let Some(next_pow) = est_pow.checked_mul(base) {
166            let cmp = next_pow.cmp(&target);
167            if cmp.is_le() {
168                est_pow = next_pow;
169                est += 1;
170            }
171            if cmp.is_ge() {
172                break;
173            }
174        }
175        (est as usize, Repr::from_dword(est_pow))
176    }
177
178    pub(crate) fn log_word_base(target: &[Word], base: Word) -> (usize, Repr) {
179        let log2_self = log2_bounds_large(target).0;
180        let (wexp, wbase) = if base == 10 {
181            // specialize for base 10, which is cached in radix_info
182            (radix::RADIX10_INFO.digits_per_word, radix::RADIX10_INFO.range_per_word)
183        } else {
184            max_exp_in_word(base)
185        };
186        let log2_wbase = wbase.log2_bounds().1;
187
188        let mut est = (log2_self * wexp as f32 / log2_wbase) as usize; // est >= 1
189        let mut est_pow = if est == 1 {
190            Repr::from_word(base)
191        } else {
192            pow::repr::pow_word_base(base, est)
193        }
194        .into_buffer();
195        assert!(cmp_in_place(&est_pow, target).is_le());
196
197        // first proceed by multiplying wbase, which should happen very rarely
198        while est_pow.len() < target.len() {
199            if est_pow.len() == target.len() - 1 {
200                let target_hi = highest_dword(target);
201                let next_hi = (extend_word(*est_pow.last().unwrap()) + 1) * extend_word(wbase); // overestimate
202                if next_hi > target_hi {
203                    break;
204                }
205            }
206            let carry = mul::mul_word_in_place(&mut est_pow, wbase);
207            est_pow.push_resizing(carry);
208            est += wexp;
209        }
210
211        // then proceed by multiplying base, which can require a few steps
212        loop {
213            match cmp_in_place(&est_pow, target) {
214                Ordering::Less => {
215                    let carry = mul::mul_word_in_place(&mut est_pow, base);
216                    est_pow.push_resizing(carry);
217                    est += 1;
218                }
219                Ordering::Equal => break,
220                Ordering::Greater => {
221                    // recover the over estimate
222                    debug_assert_zero!(div::div_by_word_in_place(&mut est_pow, base));
223                    est -= 1;
224                    break;
225                }
226            }
227        }
228
229        (est, Repr::from_buffer(est_pow))
230    }
231
232    fn log_large(target: &[Word], base: &[Word]) -> (usize, Repr) {
233        debug_assert!(cmp_in_place(target, base).is_ge()); // this ensures est >= 1
234
235        // Use power-sequence decomposition:
236        // Build base^(2^i) by repeated squaring, then binary-decompose the target.
237        // This replaces O(est_error) trial multiplications with O(log(est)) squarings + divisions.
238        // Number of squarings until base^(2^i) exceeds target is at most
239        // floor(log2(target_bits / base_bits)) + 1, plus the initial entry.
240        let target_bits = target.len() * WORD_BITS_USIZE;
241        let base_bits = base.len() * WORD_BITS_USIZE;
242        let max_powers = bit_len(target_bits / base_bits) as usize + 2;
243        let mut powers: Vec<Repr> = Vec::with_capacity(max_powers);
244        powers.push(Repr::from_buffer(Buffer::from(base))); // base^(2^0) = base^1
245
246        loop {
247            let prev = powers.last().unwrap();
248            if 2 * prev.len() - 1 > target.len() {
249                break;
250            }
251            let next = mul_ops::repr::square_large(prev.as_slice());
252            if cmp_in_place(next.as_slice(), target).is_gt() {
253                break;
254            }
255            powers.push(next);
256        }
257
258        // Binary decomposition from largest power to smallest.
259        // current holds the un-decomposed part; est accumulates the exponent.
260        let mut current = Buffer::from(target);
261        let mut est = 0usize;
262
263        for (i, p) in powers.iter().enumerate().rev() {
264            let p_words = p.as_slice();
265            if current.len() < p_words.len() {
266                continue;
267            }
268            if current.len() == p_words.len() && cmp_in_place(&current, p_words).is_lt() {
269                continue;
270            }
271
272            // current >= base^(2^i): divide current by base^(2^i)
273            let mut p_buf = Buffer::from(p_words);
274            let mut allocation = MemoryAllocation::new(crate::memory::add_layout(
275                crate::memory::array_layout::<Word>(current.len() + 1),
276                div::memory_requirement_exact(current.len(), p_buf.len()),
277            ));
278            let (shift, fast_div_top) = div::normalize(&mut p_buf);
279            let quo_carry = div::div_rem_unshifted_in_place(
280                &mut current,
281                &p_buf,
282                shift,
283                fast_div_top,
284                &mut allocation.memory(),
285            );
286            current.push_resizing(quo_carry);
287
288            // After division: current = [remainder | quotient]
289            // We keep the quotient for further decomposition
290            let n = p_buf.len();
291            debug_assert_zero!(shift::shr_in_place(&mut current[..n], shift));
292
293            // Move quotient to the front of the buffer
294            let quo_len = current.len() - n;
295            if quo_len == 0 {
296                current.truncate(0);
297            } else {
298                current.erase_front(n);
299            }
300
301            est += 1 << i;
302        }
303
304        drop(powers);
305
306        // Compute base^est for the return value
307        let est_pow = compute_power(base, est);
308
309        // Verify and fix off-by-one (shouldn't happen with exact decomposition, but be safe)
310        assert!(cmp_in_place(est_pow.as_slice(), target).is_le());
311        let next_pow = mul_ops::repr::mul_large(est_pow.as_slice(), base);
312        if cmp_in_place(next_pow.as_slice(), target).is_le() {
313            return (est + 1, next_pow);
314        }
315
316        (est, est_pow)
317    }
318
319    fn compute_power(base: &[Word], exp: usize) -> Repr {
320        if exp <= 1 {
321            Repr::from_buffer(Buffer::from(base))
322        } else if base.len() == 2 {
323            let base_dword = highest_dword(base);
324            pow::repr::pow_dword_base(base_dword, exp)
325        } else {
326            pow::repr::pow_large_base(base, exp)
327        }
328    }
329
330    #[inline]
331    fn log2_bounds_large(words: &[Word]) -> (f32, f32) {
332        // notice that the bit length can be larger than 2^24, so the result
333        // cannot be exact even if the input is a power of two
334        let hi = highest_dword(words);
335        let rem_bits = (words.len() - 2) * WORD_BITS_USIZE;
336        let (hi_lb, hi_ub) = hi.log2_bounds();
337
338        /// Adjustment required to ensure floor or ceil operation
339        const ADJUST: f32 = 2. * f32::EPSILON;
340        let est_lb = (hi_lb + rem_bits as f32) * (1. - ADJUST);
341        let est_ub = (hi_ub + rem_bits as f32) * (1. + ADJUST);
342        (est_lb, est_ub)
343    }
344}