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 core::cmp::Ordering;
71
72    use dashu_base::EstimatedLog2;
73
74    use crate::{
75        arch::word::{DoubleWord, Word},
76        buffer::Buffer,
77        cmp::cmp_in_place,
78        div,
79        error::panic_invalid_log_oprand,
80        helper_macros::debug_assert_zero,
81        math::max_exp_in_word,
82        mul, mul_ops, pow,
83        primitive::{extend_word, highest_dword, shrink_dword, split_dword, WORD_BITS_USIZE},
84        radix,
85        repr::{
86            Repr,
87            TypedReprRef::{self, *},
88        },
89    };
90
91    impl TypedReprRef<'_> {
92        /// Floor logarithm, returns (log(self), base^log(self))
93        pub fn log(self, base: TypedReprRef<'_>) -> (usize, Repr) {
94            // shortcuts
95            if let RefSmall(dw) = base {
96                match dw {
97                    0 | 1 => panic_invalid_log_oprand(),
98                    2 => {
99                        return (
100                            self.bit_len() - 1,
101                            Repr::zero().into_typed().set_bit(self.bit_len()),
102                        )
103                    }
104                    b if b.is_power_of_two() => {
105                        let base_bits = b.trailing_zeros() as usize;
106                        let exp = (self.bit_len() - 1) / base_bits;
107                        return (exp, Repr::zero().into_typed().set_bit(exp * base_bits));
108                    }
109                    _ => {}
110                }
111            }
112
113            match (self, base) {
114                (RefSmall(dword), RefSmall(base_dword)) => log_dword(dword, base_dword),
115                (RefSmall(_), RefLarge(_)) => (0, Repr::one()),
116                (RefLarge(words), RefSmall(base_dword)) => {
117                    if let Some(base_word) = shrink_dword(base_dword) {
118                        log_word_base(words, base_word)
119                    } else {
120                        let mut buffer: [Word; 2] = [0; 2];
121                        let (lo, hi) = split_dword(base_dword);
122                        buffer[0] = lo;
123                        buffer[1] = hi;
124                        log_large(words, &buffer)
125                    }
126                }
127                (RefLarge(words), RefLarge(base_words)) => match cmp_in_place(words, base_words) {
128                    Ordering::Less => (0, Repr::one()),
129                    Ordering::Equal => (1, Repr::from_buffer(Buffer::from(words))),
130                    Ordering::Greater => log_large(words, base_words),
131                },
132            }
133        }
134
135        pub fn log2_bounds(self) -> (f32, f32) {
136            match self {
137                RefSmall(dword) => dword.log2_bounds(),
138                RefLarge(words) => log2_bounds_large(words),
139            }
140        }
141    }
142
143    fn log_dword(target: DoubleWord, base: DoubleWord) -> (usize, Repr) {
144        debug_assert!(base > 1);
145
146        // shortcuts
147        match target {
148            0 => panic_invalid_log_oprand(),
149            1 => return (0, Repr::one()),
150            i if i < base => return (0, Repr::one()),
151            i if i == base => return (1, Repr::from_dword(base)),
152            _ => {}
153        }
154
155        let log2_self = target.log2_bounds().0;
156        let log2_base = base.log2_bounds().1;
157
158        let mut est = (log2_self / log2_base) as u32; // float to int is underestimate
159        let mut est_pow = base.pow(est);
160        assert!(est_pow <= target);
161
162        while let Some(next_pow) = est_pow.checked_mul(base) {
163            let cmp = next_pow.cmp(&target);
164            if cmp.is_le() {
165                est_pow = next_pow;
166                est += 1;
167            }
168            if cmp.is_ge() {
169                break;
170            }
171        }
172        (est as usize, Repr::from_dword(est_pow))
173    }
174
175    pub(crate) fn log_word_base(target: &[Word], base: Word) -> (usize, Repr) {
176        let log2_self = log2_bounds_large(target).0;
177        let (wexp, wbase) = if base == 10 {
178            // specialize for base 10, which is cached in radix_info
179            (radix::RADIX10_INFO.digits_per_word, radix::RADIX10_INFO.range_per_word)
180        } else {
181            max_exp_in_word(base)
182        };
183        let log2_wbase = wbase.log2_bounds().1;
184
185        let mut est = (log2_self * wexp as f32 / log2_wbase) as usize; // est >= 1
186        let mut est_pow = if est == 1 {
187            Repr::from_word(base)
188        } else {
189            pow::repr::pow_word_base(base, est)
190        }
191        .into_buffer();
192        assert!(cmp_in_place(&est_pow, target).is_le());
193
194        // first proceed by multiplying wbase, which should happen very rarely
195        while est_pow.len() < target.len() {
196            if est_pow.len() == target.len() - 1 {
197                let target_hi = highest_dword(target);
198                let next_hi = (extend_word(*est_pow.last().unwrap()) + 1) * extend_word(wbase); // overestimate
199                if next_hi > target_hi {
200                    break;
201                }
202            }
203            let carry = mul::mul_word_in_place(&mut est_pow, wbase);
204            est_pow.push_resizing(carry);
205            est += wexp;
206        }
207
208        // then proceed by multiplying base, which can require a few steps
209        loop {
210            match cmp_in_place(&est_pow, target) {
211                Ordering::Less => {
212                    let carry = mul::mul_word_in_place(&mut est_pow, base);
213                    est_pow.push_resizing(carry);
214                    est += 1;
215                }
216                Ordering::Equal => break,
217                Ordering::Greater => {
218                    // recover the over estimate
219                    debug_assert_zero!(div::div_by_word_in_place(&mut est_pow, base));
220                    est -= 1;
221                    break;
222                }
223            }
224        }
225
226        (est, Repr::from_buffer(est_pow))
227    }
228
229    fn log_large(target: &[Word], base: &[Word]) -> (usize, Repr) {
230        debug_assert!(cmp_in_place(target, base).is_ge()); // this ensures est >= 1
231
232        // first estimates the result
233        let log2_self = log2_bounds_large(target).0;
234        let log2_base = log2_bounds_large(base).1;
235        let mut est = (log2_self / log2_base) as usize; // float to int is underestimate
236        est = est.max(1); // sometimes est can be zero due to estimation error
237        let mut est_pow = if est == 1 {
238            Repr::from_buffer(Buffer::from(base))
239        } else if base.len() == 2 {
240            let base_dword = highest_dword(base);
241            pow::repr::pow_dword_base(base_dword, est)
242        } else {
243            pow::repr::pow_large_base(base, est)
244        };
245        assert!(cmp_in_place(est_pow.as_slice(), target).is_le());
246
247        // then fix the error by trials
248        loop {
249            let next_pow = mul_ops::repr::mul_large(est_pow.as_slice(), base);
250            let cmp = cmp_in_place(next_pow.as_slice(), target);
251            if cmp.is_le() {
252                est_pow = next_pow;
253                est += 1;
254            }
255            if cmp.is_ge() {
256                break;
257            }
258        }
259        (est, est_pow)
260    }
261
262    #[inline]
263    fn log2_bounds_large(words: &[Word]) -> (f32, f32) {
264        // notice that the bit length can be larger than 2^24, so the result
265        // cannot be exact even if the input is a power of two
266        let hi = highest_dword(words);
267        let rem_bits = (words.len() - 2) * WORD_BITS_USIZE;
268        let (hi_lb, hi_ub) = hi.log2_bounds();
269
270        /// Adjustment required to ensure floor or ceil operation
271        const ADJUST: f32 = 2. * f32::EPSILON;
272        let est_lb = (hi_lb + rem_bits as f32) * (1. - ADJUST);
273        let est_ub = (hi_ub + rem_bits as f32) * (1. + ADJUST);
274        (est_lb, est_ub)
275    }
276}