Skip to main content

malachite_float/arithmetic/
log_base_rational_rational_base.rs

1// Copyright © 2026 Mikhail Hogrefe
2//
3// This file is part of Malachite.
4//
5// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
6// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
7// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
8
9use crate::arithmetic::log_base_2::extended_log_base_2_of_rational;
10use crate::basic::extended::ExtendedFloat;
11use crate::{Float, emulate_rational_to_float_fn};
12use core::cmp::Ordering::{self, *};
13use malachite_base::num::arithmetic::traits::{CeilingLogBase2, CheckedLogBase, Sign};
14use malachite_base::num::basic::floats::PrimitiveFloat;
15use malachite_base::num::basic::integers::PrimitiveInt;
16use malachite_base::num::basic::traits::{NaN, NegativeInfinity, Zero};
17use malachite_base::num::conversion::traits::{ExactFrom, RoundingFrom};
18use malachite_base::num::factorization::traits::ExpressAsPower;
19use malachite_base::num::logic::traits::SignificantBits;
20use malachite_base::rounding_modes::RoundingMode::{self, *};
21use malachite_nz::natural::arithmetic::float_extras::float_can_round;
22use malachite_nz::platform::Limb;
23use malachite_q::Rational;
24
25// Returns `Some(log_base(x))` when it is rational, and `None` when it is irrational (or when the
26// inputs are too large for the check to be worthwhile). `x` must be positive and not equal to 1,
27// and `base` must be greater than 1.
28//
29// `log_base(x)` is rational exactly when `x` and `base` are both powers of a common rational `g`,
30// say `x = g^a` and `base = g^e_base`; then `log_base(x) = a / e_base`. Taking `g` to be the
31// primitive root of `base` (`base.express_as_power()`), this holds iff `x` is an integer power of
32// `g`, found by `Rational::checked_log_base` (which also covers `x < 1`, giving a negative `a`).
33//
34// Detecting these rational results up front is essential, not just an optimization: when the result
35// is exactly representable (for example `log_9(3) = 1/2`), the Ziv loop in
36// `log_base_rational_rational_base_prec_round_normal` would never terminate, because the rounding
37// test can never certify a value sitting exactly on a representable point or tie.
38//
39// `express_as_power` perfect-power-tests `base`'s numerator and denominator, which is infeasible
40// for an astronomically large (for example near-1) base, so the whole check is skipped when either
41// input exceeds `64 * prec` bits. Such an input cannot be a power of `g` with a representable
42// exponent at this precision (the common cases like `log_4(8)` involve tiny operands), so it is
43// left to the Ziv loop. The one residual gap is a representable *fractional* result with a
44// perfect-power base larger than the bound -- which would require multi-hundred-megabyte
45// commensurable `x` and `base` -- where the loop would not terminate; such inputs are far beyond
46// any realistic or testable range.
47pub(crate) fn rational_log_base_rational_rational_base(
48    x: &Rational,
49    base: &Rational,
50    prec: u64,
51) -> Option<Rational> {
52    let bound = prec.saturating_mul(64);
53    if x.significant_bits() > bound || base.significant_bits() > bound {
54        return None;
55    }
56    // `express_as_power` returns `None` when `base` is not a perfect power, in which case `base`
57    // itself is `g` (with exponent 1).
58    let (root, e_base) = base.express_as_power().unwrap_or_else(|| (base.clone(), 1));
59    let a = x.checked_log_base(&root)?;
60    Some(Rational::from_signeds(a, i64::exact_from(e_base)))
61}
62
63// Computes log_base(x) for `Rational` `x` and `base`, by log_base(x) = log_2(x) / log_2(base). The
64// input `x` is positive, and `base` is greater than 1.
65//
66// Both logs are computed in the extended exponent range (see `extended_log_base_2_of_rational`) so
67// that neither operand underflows when `x` or `base` is near 1, and so that their quotient may
68// temporarily leave the representable range. The single conversion back to a `Float`, via
69// `ExtendedFloat::into_float_helper`, performs the one correctly-rounded clamp to an infinity,
70// maximum, zero, or minimum as dictated by the rounding mode. This handles both overflow (a base
71// near 1, so `log_2(base)` is tiny and the quotient is huge) and underflow (an `x` near 1, so
72// `log_2(x)` is tiny).
73fn log_base_rational_rational_base_prec_round_normal(
74    x: &Rational,
75    base: &Rational,
76    prec: u64,
77    rm: RoundingMode,
78) -> (Float, Ordering) {
79    // If x is 1, the result is 0.
80    if *x == 1u32 {
81        return (Float::ZERO, Equal);
82    }
83    // If log_base(x) is rational -- x and base are both powers of a common rational -- compute it
84    // directly. This includes exactly-representable results (which the Ziv loop could never
85    // certify) as well as non-representable rationals (cheaper and exact this way).
86    if let Some(q) = rational_log_base_rational_rational_base(x, base, prec) {
87        return Float::from_rational_prec_round(q, prec, rm);
88    }
89    // The result is irrational, so it is never exactly representable.
90    assert_ne!(rm, Exact, "Inexact log_base_rational_rational_base");
91    // The initial slack keeps working_prec at least 7, so the working_prec - 6 below stays
92    // positive.
93    let mut working_prec = prec + 6 + prec.ceiling_log_base_2();
94    let mut increment = Limb::WIDTH;
95    loop {
96        // log_2(x), extended (finite and nonzero, since x is positive and not 1).
97        let num = extended_log_base_2_of_rational(x, working_prec);
98        // log_2(base) > 0, extended.
99        let den = extended_log_base_2_of_rational(base, working_prec);
100        // log_2(x) / log_2(base) in the extended range; cannot overflow or underflow here.
101        let (quotient, _) = num.div_prec_val_ref(&den, working_prec);
102        // Each log is accurate to within 2 ulps and the division adds at most 1 more, for at most 5
103        // ulps total; working_prec - 6 correct bits comfortably suffice for the rounding test.
104        if float_can_round(
105            quotient.x.significand_ref().unwrap(),
106            working_prec - 6,
107            prec,
108            rm,
109        ) {
110            // Round the mantissa to prec, then place the extended exponent, clamping once to the
111            // Float range as the rounding mode dictates.
112            let (rounded, o) = Float::from_float_prec_round(quotient.x, prec, rm);
113            let mut result = ExtendedFloat::from(rounded);
114            result.exp = result.exp.checked_add(quotient.exp).unwrap();
115            return result.into_float_helper(prec, rm, o);
116        }
117        // Increase the precision.
118        working_prec += increment;
119        increment = working_prec >> 1;
120    }
121}
122
123impl Float {
124    /// Computes $\log_b x$, where $x$ and the base $b$ are both [`Rational`]s with $b>1$, returning
125    /// a [`Float`] rounded to the specified precision and with the specified rounding mode. Both
126    /// are taken by value. An [`Ordering`] is also returned, indicating whether the rounded value
127    /// is less than, equal to, or greater than the exact value. Although `NaN`s are not comparable
128    /// to any [`Float`], whenever this function returns a `NaN` it also returns `Equal`.
129    ///
130    /// This computes $\log_2 x / \log_2 b$. Both logarithms are evaluated in an extended exponent
131    /// range, so that an $x$ or $b$ extremely close to 1 (where the logarithm is tiny) does not
132    /// lose accuracy, and the single conversion of the quotient back to a [`Float`] performs the
133    /// one correctly-rounded clamp.
134    ///
135    /// See [`RoundingMode`] for a description of the possible rounding modes.
136    ///
137    /// $$
138    /// f(x,b,p,m) = \log_b x+\varepsilon.
139    /// $$
140    /// - If $\log_b x$ is infinite, zero, or `NaN`, $\varepsilon$ may be ignored or assumed to be
141    ///   0.
142    /// - If $\log_b x$ is finite and nonzero, and $m$ is not `Nearest`, then $|\varepsilon| <
143    ///   2^{\lfloor\log_2 |\log_b x|\rfloor-p+1}$.
144    /// - If $\log_b x$ is finite and nonzero, and $m$ is `Nearest`, then $|\varepsilon| \leq
145    ///   2^{\lfloor\log_2 |\log_b x|\rfloor-p}$.
146    ///
147    /// If the output has a precision, it is `prec`.
148    ///
149    /// Special cases:
150    /// - $f(0,b,p,m)=-\infty$
151    /// - $f(x,b,p,m)=\text{NaN}$ for $x<0$
152    /// - $f(1,b,p,m)=0$
153    /// - $f(x,b,p,m)=a/e$ when $x=g^a$, where $g$ is the primitive root of $b$ and $b=g^e$, rounded
154    ///   to precision $p$; the result is exact if and only if $a/e$ is representable with precision
155    ///   $p$ (for example $\log_4 8=3/2$ is exact)
156    ///
157    /// Like a logarithm of a [`Float`] with a [`Rational`] base, this can both overflow (for a base
158    /// near 1) and underflow (for an $x$ near 1).
159    ///
160    /// # Worst-case complexity
161    /// $T(n) = O(n (\log n)^2 \log\log n)$
162    ///
163    /// $M(n) = O(n (\log n)^2)$
164    ///
165    /// where $T$ is time, $M$ is additional memory, and $n$ is `prec`.
166    ///
167    /// # Panics
168    /// Panics if `prec` is zero, if `base` is less than or equal to 1, or if `rm` is `Exact` but
169    /// the result cannot be represented exactly with the given precision.
170    ///
171    /// # Examples
172    /// ```
173    /// use malachite_base::rounding_modes::RoundingMode::*;
174    /// use malachite_float::Float;
175    /// use malachite_q::Rational;
176    /// use std::cmp::Ordering::*;
177    ///
178    /// let (log, o) = Float::log_base_rational_rational_base_prec_round(
179    ///     Rational::from(8),
180    ///     Rational::from(4),
181    ///     10,
182    ///     Exact,
183    /// );
184    /// assert_eq!(log.to_string(), "1.5"); // log_4(8) = 3/2
185    /// assert_eq!(o, Equal);
186    ///
187    /// let (log, o) = Float::log_base_rational_rational_base_prec_round(
188    ///     Rational::from(2),
189    ///     Rational::from(3),
190    ///     10,
191    ///     Floor,
192    /// );
193    /// assert_eq!(log.to_string(), "0.631"); // log_3(2) = 0.6309...
194    /// assert_eq!(o, Less);
195    /// ```
196    #[allow(clippy::needless_pass_by_value)]
197    #[inline]
198    pub fn log_base_rational_rational_base_prec_round(
199        x: Rational,
200        base: Rational,
201        prec: u64,
202        rm: RoundingMode,
203    ) -> (Self, Ordering) {
204        Self::log_base_rational_rational_base_prec_round_ref(&x, &base, prec, rm)
205    }
206
207    /// Computes $\log_b x$, where $x$ and the base $b$ are both [`Rational`]s with $b>1$, returning
208    /// a [`Float`] rounded to the specified precision and with the specified rounding mode. Both
209    /// are taken by reference. An [`Ordering`] is also returned, indicating whether the rounded
210    /// value is less than, equal to, or greater than the exact value.
211    ///
212    /// See [`Float::log_base_rational_rational_base_prec_round`] for details, special cases, and a
213    /// description of the rounding behavior.
214    ///
215    /// # Worst-case complexity
216    /// $T(n) = O(n (\log n)^2 \log\log n)$
217    ///
218    /// $M(n) = O(n (\log n)^2)$
219    ///
220    /// where $T$ is time, $M$ is additional memory, and $n$ is `prec`.
221    ///
222    /// # Panics
223    /// Panics if `prec` is zero, if `base` is less than or equal to 1, or if `rm` is `Exact` but
224    /// the result cannot be represented exactly with the given precision.
225    ///
226    /// # Examples
227    /// ```
228    /// use malachite_base::rounding_modes::RoundingMode::*;
229    /// use malachite_float::Float;
230    /// use malachite_q::Rational;
231    /// use std::cmp::Ordering::*;
232    ///
233    /// let (log, o) = Float::log_base_rational_rational_base_prec_round_ref(
234    ///     &Rational::from(9),
235    ///     &Rational::from(3),
236    ///     10,
237    ///     Exact,
238    /// );
239    /// assert_eq!(log.to_string(), "2.0"); // log_3(9) = 2
240    /// assert_eq!(o, Equal);
241    ///
242    /// let (log, o) = Float::log_base_rational_rational_base_prec_round_ref(
243    ///     &Rational::from_signeds(1, 8),
244    ///     &Rational::from(2),
245    ///     10,
246    ///     Exact,
247    /// );
248    /// assert_eq!(log.to_string(), "-3.0"); // log_2(1/8) = -3
249    /// assert_eq!(o, Equal);
250    /// ```
251    pub fn log_base_rational_rational_base_prec_round_ref(
252        x: &Rational,
253        base: &Rational,
254        prec: u64,
255        rm: RoundingMode,
256    ) -> (Self, Ordering) {
257        assert_ne!(prec, 0);
258        assert!(*base > 1u32, "Logarithm base must be greater than 1");
259        match x.sign() {
260            Less => (Self::NAN, Equal),
261            Equal => (Self::NEGATIVE_INFINITY, Equal),
262            Greater => log_base_rational_rational_base_prec_round_normal(x, base, prec, rm),
263        }
264    }
265
266    /// Computes $\log_b x$, where $x$ and the base $b$ are both [`Rational`]s with $b>1$, returning
267    /// a [`Float`] rounded to the nearest value of the specified precision. Both are taken by
268    /// value. An [`Ordering`] is also returned.
269    ///
270    /// See [`Float::log_base_rational_rational_base_prec_round`] for details and special cases.
271    ///
272    /// # Worst-case complexity
273    /// $T(n) = O(n (\log n)^2 \log\log n)$
274    ///
275    /// $M(n) = O(n (\log n)^2)$
276    ///
277    /// where $T$ is time, $M$ is additional memory, and $n$ is `prec`.
278    ///
279    /// # Panics
280    /// Panics if `prec` is zero or if `base` is less than or equal to 1.
281    ///
282    /// # Examples
283    /// ```
284    /// use malachite_float::Float;
285    /// use malachite_q::Rational;
286    /// use std::cmp::Ordering::*;
287    ///
288    /// let (log, o) =
289    ///     Float::log_base_rational_rational_base_prec(Rational::from(8), Rational::from(4), 10);
290    /// assert_eq!(log.to_string(), "1.5"); // log_4(8) = 3/2
291    /// assert_eq!(o, Equal);
292    /// ```
293    #[allow(clippy::needless_pass_by_value)]
294    #[inline]
295    pub fn log_base_rational_rational_base_prec(
296        x: Rational,
297        base: Rational,
298        prec: u64,
299    ) -> (Self, Ordering) {
300        Self::log_base_rational_rational_base_prec_round_ref(&x, &base, prec, Nearest)
301    }
302
303    /// Computes $\log_b x$, where $x$ and the base $b$ are both [`Rational`]s with $b>1$, returning
304    /// a [`Float`] rounded to the nearest value of the specified precision. Both are taken by
305    /// reference. An [`Ordering`] is also returned.
306    ///
307    /// See [`Float::log_base_rational_rational_base_prec_round`] for details and special cases.
308    ///
309    /// # Worst-case complexity
310    /// $T(n) = O(n (\log n)^2 \log\log n)$
311    ///
312    /// $M(n) = O(n (\log n)^2)$
313    ///
314    /// where $T$ is time, $M$ is additional memory, and $n$ is `prec`.
315    ///
316    /// # Panics
317    /// Panics if `prec` is zero or if `base` is less than or equal to 1.
318    ///
319    /// # Examples
320    /// ```
321    /// use malachite_float::Float;
322    /// use malachite_q::Rational;
323    /// use std::cmp::Ordering::*;
324    ///
325    /// let (log, o) = Float::log_base_rational_rational_base_prec_ref(
326    ///     &Rational::from(9),
327    ///     &Rational::from(3),
328    ///     10,
329    /// );
330    /// assert_eq!(log.to_string(), "2.0"); // log_3(9) = 2
331    /// assert_eq!(o, Equal);
332    /// ```
333    #[inline]
334    pub fn log_base_rational_rational_base_prec_ref(
335        x: &Rational,
336        base: &Rational,
337        prec: u64,
338    ) -> (Self, Ordering) {
339        Self::log_base_rational_rational_base_prec_round_ref(x, base, prec, Nearest)
340    }
341}
342
343/// Computes $\log_b x$, the base-$b$ logarithm of a [`Rational`], where the base $b$ is also a
344/// [`Rational`] greater than 1, returning a primitive float result. Using this function is more
345/// accurate than computing the logarithm using the standard library, whose logarithm functions are
346/// not always correctly rounded.
347///
348/// If the logarithm is equidistant from two primitive floats, the primitive float with fewer 1s in
349/// its binary expansion is chosen. See [`RoundingMode`] for a description of the `Nearest` rounding
350/// mode.
351///
352/// The base-$b$ logarithm of any negative number is `NaN`.
353///
354/// $$
355/// f(x,b) = \log_b x+\varepsilon.
356/// $$
357/// - If $\log_b x$ is infinite, zero, or `NaN`, $\varepsilon$ may be ignored or assumed to be 0.
358/// - If $\log_b x$ is finite and nonzero, then $|\varepsilon| < 2^{\lfloor\log_2 |\log_b
359///   x|\rfloor-p}$, where $p$ is precision of the output (typically 24 if `T` is a [`f32`] and 53
360///   if `T` is a [`f64`], but less if the output is subnormal).
361///
362/// Special cases:
363/// - $f(0,b)=-\infty$
364/// - $f(x,b)=\text{NaN}$ for $x<0$
365/// - $f(1,b)=0.0$
366///
367/// Unlike a logarithm with an integer base, this function can both overflow (for a base near 1) and
368/// underflow (for an $x$ near 1).
369///
370/// # Worst-case complexity
371/// Constant time and additional memory.
372///
373/// # Panics
374/// Panics if `base` is less than or equal to 1.
375///
376/// # Examples
377/// ```
378/// use malachite_base::num::basic::traits::{NegativeInfinity, Zero};
379/// use malachite_base::num::float::NiceFloat;
380/// use malachite_float::arithmetic::log_base_rational_rational_base::*;
381/// use malachite_q::Rational;
382///
383/// assert_eq!(
384///     NiceFloat(primitive_float_log_base_rational_rational_base::<f32>(
385///         &Rational::ZERO,
386///         &Rational::from(10)
387///     )),
388///     NiceFloat(f32::NEGATIVE_INFINITY)
389/// );
390/// // log_4(8) = 3/2
391/// assert_eq!(
392///     NiceFloat(primitive_float_log_base_rational_rational_base::<f32>(
393///         &Rational::from(8),
394///         &Rational::from(4)
395///     )),
396///     NiceFloat(1.5)
397/// );
398/// // log_(3/2)(9/4) = 2
399/// assert_eq!(
400///     NiceFloat(primitive_float_log_base_rational_rational_base::<f32>(
401///         &Rational::from_unsigneds(9u8, 4),
402///         &Rational::from_unsigneds(3u8, 2)
403///     )),
404///     NiceFloat(2.0)
405/// );
406/// // log_10(50)
407/// assert_eq!(
408///     NiceFloat(primitive_float_log_base_rational_rational_base::<f32>(
409///         &Rational::from(50),
410///         &Rational::from(10)
411///     )),
412///     NiceFloat(1.69897)
413/// );
414/// assert_eq!(
415///     NiceFloat(primitive_float_log_base_rational_rational_base::<f32>(
416///         &Rational::from(-1000),
417///         &Rational::from(10)
418///     )),
419///     NiceFloat(f32::NAN)
420/// );
421/// ```
422#[inline]
423#[allow(clippy::type_repetition_in_bounds)]
424pub fn primitive_float_log_base_rational_rational_base<T: PrimitiveFloat>(
425    x: &Rational,
426    base: &Rational,
427) -> T
428where
429    Float: PartialOrd<T>,
430    for<'a> T: ExactFrom<&'a Float> + RoundingFrom<&'a Float>,
431{
432    emulate_rational_to_float_fn(
433        |x, prec| Float::log_base_rational_rational_base_prec_ref(x, base, prec),
434        x,
435    )
436}