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}