malachite_q/arithmetic/
log_base.rs

1// Copyright © 2025 Mikhail Hogrefe
2//
3// Uses code adopted from the FLINT Library.
4//
5//      Copyright (C) 2011 Fredrik Johansson
6//
7// This file is part of Malachite.
8//
9// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
10// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
11// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
12
13use crate::Rational;
14use core::cmp::Ordering::*;
15#[cfg(not(any(feature = "test_build", feature = "random")))]
16use malachite_base::num::arithmetic::traits::Ln;
17use malachite_base::num::arithmetic::traits::{
18    CeilingLogBase, CeilingLogBasePowerOf2, CheckedLogBase, CheckedLogBase2,
19    CheckedLogBasePowerOf2, FloorLogBase, FloorLogBasePowerOf2, Pow,
20};
21use malachite_base::num::comparison::traits::OrdAbs;
22use malachite_base::num::conversion::traits::{RoundingFrom, SciMantissaAndExponent};
23use malachite_base::rounding_modes::RoundingMode::*;
24
25fn approx_log_helper(x: &Rational) -> f64 {
26    let (mantissa, exponent): (f64, i64) = x.sci_mantissa_and_exponent();
27    mantissa.ln() + (exponent as f64) * core::f64::consts::LN_2
28}
29
30impl Rational {
31    /// Calculates the approximate natural logarithm of a positive [`Rational`].
32    ///
33    /// $f(x) = (1+\varepsilon)(\log x)$, where $|\varepsilon| < 2^{-52}.$
34    ///
35    /// # Worst-case complexity
36    /// $T(n) = O(n)$
37    ///
38    /// $M(n) = O(1)$
39    ///
40    /// where $T$ is time, $M$ is additional memory, and $n$ is `self.significant_bits()`.
41    ///
42    /// # Examples
43    /// ```
44    /// use malachite_base::num::arithmetic::traits::{Pow, PowerOf2};
45    /// use malachite_base::num::float::NiceFloat;
46    /// use malachite_q::Rational;
47    ///
48    /// assert_eq!(
49    ///     NiceFloat(Rational::from(10i32).approx_log()),
50    ///     NiceFloat(2.3025850929940455)
51    /// );
52    /// assert_eq!(
53    ///     NiceFloat(Rational::from(10i32).pow(100u64).approx_log()),
54    ///     NiceFloat(230.25850929940455)
55    /// );
56    /// assert_eq!(
57    ///     NiceFloat(Rational::power_of_2(1000000u64).approx_log()),
58    ///     NiceFloat(693147.1805599453)
59    /// );
60    /// assert_eq!(
61    ///     NiceFloat(Rational::power_of_2(-1000000i64).approx_log()),
62    ///     NiceFloat(-693147.1805599453)
63    /// );
64    /// ```
65    ///
66    /// This is equivalent to `fmpz_dlog` from `fmpz/dlog.c`, FLINT 2.7.1.
67    #[inline]
68    pub fn approx_log(&self) -> f64 {
69        assert!(*self > 0u32);
70        approx_log_helper(self)
71    }
72}
73
74// # Worst-case complexity
75// $T(n, m) = O(nm \log (nm) \log\log (nm))$
76//
77// $M(n, m) = O(nm \log (nm))$
78//
79// where $T$ is time, $M$ is additional memory, $n$ is `base.significant_bits()`, and $m$ is
80// $|\log_b x|$, where $b$ is `base` and $x$ is `x`.
81pub(crate) fn log_base_helper(x: &Rational, base: &Rational) -> (i64, bool) {
82    assert!(*base > 0u32);
83    assert_ne!(*base, 1u32);
84    if *x == 1u32 {
85        return (0, true);
86    }
87    let mut log = i64::rounding_from(approx_log_helper(x) / approx_log_helper(base), Floor).0;
88    let mut power = base.pow(log);
89    if *base > 1u32 {
90        match power.cmp_abs(x) {
91            Equal => (log, true),
92            Less => loop {
93                power *= base;
94                match power.cmp_abs(x) {
95                    Equal => {
96                        return (log + 1, true);
97                    }
98                    Less => {
99                        log += 1;
100                    }
101                    Greater => {
102                        return (log, false);
103                    }
104                }
105            },
106            Greater => loop {
107                power /= base;
108                match power.cmp_abs(x) {
109                    Equal => {
110                        return (log - 1, true);
111                    }
112                    Less => {
113                        return (log - 1, false);
114                    }
115                    Greater => {
116                        log -= 1;
117                    }
118                }
119            },
120        }
121    } else {
122        match power.cmp_abs(x) {
123            Equal => (log, true),
124            Less => loop {
125                power /= base;
126                match power.cmp_abs(x) {
127                    Equal => {
128                        return (log - 1, true);
129                    }
130                    Less => {
131                        log -= 1;
132                    }
133                    Greater => {
134                        return (log - 1, false);
135                    }
136                }
137            },
138            Greater => loop {
139                power *= base;
140                match power.cmp_abs(x) {
141                    Equal => {
142                        return (log + 1, true);
143                    }
144                    Less => {
145                        return (log, false);
146                    }
147                    Greater => {
148                        log += 1;
149                    }
150                }
151            },
152        }
153    }
154}
155
156impl FloorLogBase<&Rational> for &Rational {
157    type Output = i64;
158
159    /// Returns the floor of the base-$b$ logarithm of a positive [`Rational`].
160    ///
161    /// Note that this function may be slow if the base is very close to 1.
162    ///
163    /// $f(x, b) = \lfloor\log_b x\rfloor$.
164    ///
165    /// # Worst-case complexity
166    /// $T(n, m) = O(nm \log (nm) \log\log (nm))$
167    ///
168    /// $M(n, m) = O(nm \log (nm))$
169    ///
170    /// where $T$ is time, $M$ is additional memory, $n$ is `base.significant_bits()`, and $m$ is
171    /// $|\log_b x|$, where $b$ is `base` and $x$ is `x`.
172    ///
173    /// # Panics
174    /// Panics if `self` less than or equal to zero or `base` is 1.
175    ///
176    /// # Examples
177    /// ```
178    /// use malachite_base::num::arithmetic::traits::FloorLogBase;
179    /// use malachite_q::Rational;
180    ///
181    /// assert_eq!(
182    ///     Rational::from(80u32).floor_log_base(&Rational::from(3u32)),
183    ///     3
184    /// );
185    /// assert_eq!(
186    ///     Rational::from(81u32).floor_log_base(&Rational::from(3u32)),
187    ///     4
188    /// );
189    /// assert_eq!(
190    ///     Rational::from(82u32).floor_log_base(&Rational::from(3u32)),
191    ///     4
192    /// );
193    /// assert_eq!(
194    ///     Rational::from(4294967296u64).floor_log_base(&Rational::from(10u32)),
195    ///     9
196    /// );
197    /// assert_eq!(
198    ///     Rational::from_signeds(936851431250i64, 1397).floor_log_base(&Rational::from(10u32)),
199    ///     8
200    /// );
201    /// assert_eq!(
202    ///     Rational::from_signeds(5153632, 16807).floor_log_base(&Rational::from_signeds(22, 7)),
203    ///     5
204    /// );
205    /// ```
206    fn floor_log_base(self, base: &Rational) -> i64 {
207        assert!(*self > 0u32);
208        if let Some(log_base) = base.checked_log_base_2() {
209            return self.floor_log_base_power_of_2(log_base);
210        }
211        log_base_helper(self, base).0
212    }
213}
214
215impl CeilingLogBase<&Rational> for &Rational {
216    type Output = i64;
217
218    /// Returns the ceiling of the base-$b$ logarithm of a positive [`Rational`].
219    ///
220    /// Note that this function may be slow if the base is very close to 1.
221    ///
222    /// $f(x, b) = \lceil\log_b x\rceil$.
223    ///
224    /// # Worst-case complexity
225    /// $T(n, m) = O(nm \log (nm) \log\log (nm))$
226    ///
227    /// $M(n, m) = O(nm \log (nm))$
228    ///
229    /// where $T$ is time, $M$ is additional memory, $n$ is `base.significant_bits()`, and $m$ is
230    /// $|\log_b x|$, where $b$ is `base` and $x$ is `x`.
231    ///
232    /// # Panics
233    /// Panics if `self` less than or equal to zero or `base` is 1.
234    ///
235    /// # Examples
236    /// ```
237    /// use malachite_base::num::arithmetic::traits::CeilingLogBase;
238    /// use malachite_q::Rational;
239    ///
240    /// assert_eq!(
241    ///     Rational::from(80u32).ceiling_log_base(&Rational::from(3u32)),
242    ///     4
243    /// );
244    /// assert_eq!(
245    ///     Rational::from(81u32).ceiling_log_base(&Rational::from(3u32)),
246    ///     4
247    /// );
248    /// assert_eq!(
249    ///     Rational::from(82u32).ceiling_log_base(&Rational::from(3u32)),
250    ///     5
251    /// );
252    /// assert_eq!(
253    ///     Rational::from(4294967296u64).ceiling_log_base(&Rational::from(10u32)),
254    ///     10
255    /// );
256    /// assert_eq!(
257    ///     Rational::from_signeds(936851431250i64, 1397).ceiling_log_base(&Rational::from(10u32)),
258    ///     9
259    /// );
260    /// assert_eq!(
261    ///     Rational::from_signeds(5153632, 16807).ceiling_log_base(&Rational::from_signeds(22, 7)),
262    ///     5
263    /// );
264    /// ```
265    fn ceiling_log_base(self, base: &Rational) -> i64 {
266        assert!(*self > 0u32);
267        if let Some(log_base) = base.checked_log_base_2() {
268            return self.ceiling_log_base_power_of_2(log_base);
269        }
270        let (log, exact) = log_base_helper(self, base);
271        if exact { log } else { log + 1 }
272    }
273}
274
275impl CheckedLogBase<&Rational> for &Rational {
276    type Output = i64;
277
278    /// Returns the base-$b$ logarithm of a positive [`Rational`]. If the [`Rational`] is not a
279    /// power of $b$, then `None` is returned.
280    ///
281    /// Note that this function may be slow if the base is very close to 1.
282    ///
283    /// $$
284    /// f(x, b) = \\begin{cases}
285    ///     \operatorname{Some}(\log_b x) & \text{if} \\quad \log_b x \in \Z, \\\\
286    ///     \operatorname{None} & \textrm{otherwise}.
287    /// \\end{cases}
288    /// $$
289    ///
290    /// # Worst-case complexity
291    /// $T(n, m) = O(nm \log (nm) \log\log (nm))$
292    ///
293    /// $M(n, m) = O(nm \log (nm))$
294    ///
295    /// where $T$ is time, $M$ is additional memory, $n$ is `base.significant_bits()`, and $m$ is
296    /// $|\log_b x|$, where $b$ is `base` and $x$ is `x`.
297    ///
298    /// # Panics
299    /// Panics if `self` less than or equal to zero or `base` is 1.
300    ///
301    /// # Examples
302    /// ```
303    /// use malachite_base::num::arithmetic::traits::CheckedLogBase;
304    /// use malachite_q::Rational;
305    ///
306    /// assert_eq!(
307    ///     Rational::from(80u32).checked_log_base(&Rational::from(3u32)),
308    ///     None
309    /// );
310    /// assert_eq!(
311    ///     Rational::from(81u32).checked_log_base(&Rational::from(3u32)),
312    ///     Some(4)
313    /// );
314    /// assert_eq!(
315    ///     Rational::from(82u32).checked_log_base(&Rational::from(3u32)),
316    ///     None
317    /// );
318    /// assert_eq!(
319    ///     Rational::from(4294967296u64).checked_log_base(&Rational::from(10u32)),
320    ///     None
321    /// );
322    /// assert_eq!(
323    ///     Rational::from_signeds(936851431250i64, 1397).checked_log_base(&Rational::from(10u32)),
324    ///     None
325    /// );
326    /// assert_eq!(
327    ///     Rational::from_signeds(5153632, 16807).checked_log_base(&Rational::from_signeds(22, 7)),
328    ///     Some(5)
329    /// );
330    /// ```
331    fn checked_log_base(self, base: &Rational) -> Option<i64> {
332        assert!(*self > 0u32);
333        if let Some(log_base) = base.checked_log_base_2() {
334            return self.checked_log_base_power_of_2(log_base);
335        }
336        let (log, exact) = log_base_helper(self, base);
337        if exact { Some(log) } else { None }
338    }
339}