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