malachite_q/arithmetic/
approximate.rs

1// Copyright © 2025 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::Rational;
10use crate::arithmetic::traits::{Approximate, ApproximateAssign};
11use core::mem::swap;
12use malachite_base::num::arithmetic::traits::{
13    AddMulAssign, DivMod, Floor, Parity, Reciprocal, ShrRound, UnsignedAbs,
14};
15use malachite_base::num::basic::traits::{One, Zero};
16use malachite_base::num::comparison::traits::PartialOrdAbs;
17use malachite_base::num::conversion::traits::RoundingFrom;
18use malachite_base::rounding_modes::RoundingMode::*;
19use malachite_nz::integer::Integer;
20use malachite_nz::natural::Natural;
21
22fn approximate_helper(q: &Rational, max_denominator: &Natural) -> Rational {
23    let floor = q.floor();
24    let mut x = (q - Rational::from(&floor)).reciprocal();
25    let mut previous_numerator = Integer::ONE;
26    let mut previous_denominator = Natural::ZERO;
27    let mut numerator = floor;
28    let mut denominator = Natural::ONE;
29    let mut result = None;
30    loop {
31        let n;
32        (n, x.numerator) = (&x.numerator).div_mod(&x.denominator);
33        swap(&mut x.numerator, &mut x.denominator);
34        let previous_previous_numerator = previous_numerator.clone();
35        let previous_previous_denominator = previous_denominator.clone();
36        previous_numerator.add_mul_assign(&numerator, Integer::from(&n));
37        previous_denominator.add_mul_assign(&denominator, &n);
38        if previous_denominator > *max_denominator {
39            previous_numerator = previous_previous_numerator;
40            previous_denominator = previous_previous_denominator;
41            // We need a term m such that previous_denominator + denominator * m is as large as
42            // possible without exceeding max_denominator.
43            let m = (max_denominator - &previous_denominator) / &denominator;
44            let half_n = (&n).shr_round(1, Ceiling).0;
45            if m < half_n {
46            } else if m == half_n && n.even() {
47                let previous_convergent = Rational {
48                    sign: numerator >= 0u32,
49                    numerator: (&numerator).unsigned_abs(),
50                    denominator: denominator.clone(),
51                };
52                previous_numerator.add_mul_assign(&numerator, Integer::from(&m));
53                previous_denominator.add_mul_assign(&denominator, m);
54                let candidate = Rational {
55                    sign: previous_numerator >= 0u32,
56                    numerator: previous_numerator.unsigned_abs(),
57                    denominator: previous_denominator,
58                };
59                result = Some(if (q - &previous_convergent).lt_abs(&(q - &candidate)) {
60                    previous_convergent
61                } else {
62                    candidate
63                });
64            } else {
65                numerator *= Integer::from(&m);
66                numerator += previous_numerator;
67                denominator *= m;
68                denominator += previous_denominator;
69            }
70            break;
71        }
72        swap(&mut numerator, &mut previous_numerator);
73        swap(&mut denominator, &mut previous_denominator);
74    }
75    let result = if let Some(result) = result {
76        result
77    } else {
78        Rational {
79            sign: numerator >= 0u32,
80            numerator: numerator.unsigned_abs(),
81            denominator,
82        }
83    };
84    // Suppose the input is (1/4, 2). The approximations 0 and 1/2 both satisfy the denominator
85    // limit and are equidistant from 1/4, but we prefer 0 because it has the smaller denominator.
86    // Unfortunately, the code above makes the wrong choice, so we need the following code to check
87    // whether the approximation on the opposite side of `self` is better.
88    let opposite: Rational = (q << 1) - &result;
89    if result.denominator_ref() <= opposite.denominator_ref() {
90        result
91    } else {
92        opposite
93    }
94}
95
96impl Approximate for Rational {
97    /// Finds the best approximation of a [`Rational`] using a denominator no greater than a
98    /// specified maximum, taking the [`Rational`] by value.
99    ///
100    /// Let $f(x, d) = p/q$, with $p$ and $q$ relatively prime. Then the following properties hold:
101    /// - $q \leq d$
102    /// - For all $n \in \Z$ and all $m \in \Z$ with $0 < m \leq d$, $|x - p/q| \leq |x - n/m|$.
103    /// - If $|x - n/m| = |x - p/q|$, then $q \leq m$.
104    /// - If $|x - n/q| = |x - p/q|$, then $p$ is even and $n$ is either equal to $p$ or odd.
105    ///
106    /// # Worst-case complexity
107    /// $T(n) = O(n^2 \log n \log\log n)$
108    ///
109    /// $M(n) = O(n \log n)$
110    ///
111    /// where $T$ is time, $M$ is additional memory, and $n$ is `max(self.significant_bits(),
112    /// other.significant_bits())`.
113    ///
114    /// # Panics
115    /// - If `max_denominator` is zero.
116    ///
117    /// # Examples
118    /// ```
119    /// use malachite_base::num::conversion::traits::ExactFrom;
120    /// use malachite_nz::natural::Natural;
121    /// use malachite_q::arithmetic::traits::Approximate;
122    /// use malachite_q::Rational;
123    ///
124    /// assert_eq!(
125    ///     Rational::exact_from(std::f64::consts::PI)
126    ///         .approximate(&Natural::from(1000u32))
127    ///         .to_string(),
128    ///     "355/113"
129    /// );
130    /// assert_eq!(
131    ///     Rational::from_signeds(333i32, 1000)
132    ///         .approximate(&Natural::from(100u32))
133    ///         .to_string(),
134    ///     "1/3"
135    /// );
136    /// ```
137    ///
138    /// # Implementation notes
139    /// This algorithm follows the description in
140    /// <https://en.wikipedia.org/wiki/Continued_fraction#Best_rational_approximations>. One part of
141    /// the algorithm not mentioned in that article is that if the last term $n$ in the continued
142    /// fraction needs to be reduced, the optimal replacement term $m$ may be found using division.
143    fn approximate(self, max_denominator: &Natural) -> Rational {
144        assert_ne!(*max_denominator, 0);
145        if self.denominator_ref() <= max_denominator {
146            return self;
147        }
148        if *max_denominator == 1u32 {
149            return Self::from(Integer::rounding_from(self, Nearest).0);
150        }
151        approximate_helper(&self, max_denominator)
152    }
153}
154
155impl Approximate for &Rational {
156    /// Finds the best approximation of a [`Rational`] using a denominator no greater than a
157    /// specified maximum, taking the [`Rational`] by reference.
158    ///
159    /// Let $f(x, d) = p/q$, with $p$ and $q$ relatively prime. Then the following properties hold:
160    /// - $q \leq d$
161    /// - For all $n \in \Z$ and all $m \in \Z$ with $0 < m \leq d$, $|x - p/q| \leq |x - n/m|$.
162    /// - If $|x - n/m| = |x - p/q|$, then $q \leq m$.
163    /// - If $|x - n/q| = |x - p/q|$, then $p$ is even and $n$ is either equal to $p$ or odd.
164    ///
165    /// # Worst-case complexity
166    /// $T(n) = O(n^2 \log n \log\log n)$
167    ///
168    /// $M(n) = O(n \log n)$
169    ///
170    /// where $T$ is time, $M$ is additional memory, and $n$ is `max(self.significant_bits(),
171    /// other.significant_bits())`.
172    ///
173    /// # Panics
174    /// - If `max_denominator` is zero.
175    ///
176    /// # Examples
177    /// ```
178    /// use malachite_base::num::conversion::traits::ExactFrom;
179    /// use malachite_nz::natural::Natural;
180    /// use malachite_q::arithmetic::traits::Approximate;
181    /// use malachite_q::Rational;
182    ///
183    /// assert_eq!(
184    ///     (&Rational::exact_from(std::f64::consts::PI))
185    ///         .approximate(&Natural::from(1000u32))
186    ///         .to_string(),
187    ///     "355/113"
188    /// );
189    /// assert_eq!(
190    ///     (&Rational::from_signeds(333i32, 1000))
191    ///         .approximate(&Natural::from(100u32))
192    ///         .to_string(),
193    ///     "1/3"
194    /// );
195    /// ```
196    ///
197    /// # Implementation notes
198    /// This algorithm follows the description in
199    /// <https://en.wikipedia.org/wiki/Continued_fraction#Best_rational_approximations>. One part of
200    /// the algorithm not mentioned in that article is that if the last term $n$ in the continued
201    /// fraction needs to be reduced, the optimal replacement term $m$ may be found using division.
202    fn approximate(self, max_denominator: &Natural) -> Rational {
203        assert_ne!(*max_denominator, 0);
204        if self.denominator_ref() <= max_denominator {
205            return self.clone();
206        }
207        if *max_denominator == 1u32 {
208            return Rational::from(Integer::rounding_from(self, Nearest).0);
209        }
210        approximate_helper(self, max_denominator)
211    }
212}
213
214impl ApproximateAssign for Rational {
215    /// Finds the best approximation of a [`Rational`] using a denominator no greater than a
216    /// specified maximum, mutating the [`Rational`] in place.
217    ///
218    /// See [`Rational::approximate`] for more information.
219    ///
220    /// # Worst-case complexity
221    /// $T(n) = O(n^2 \log n \log\log n)$
222    ///
223    /// $M(n) = O(n \log n)$
224    ///
225    /// where $T$ is time, $M$ is additional memory, and $n$ is `max(self.significant_bits(),
226    /// other.significant_bits())`.
227    ///
228    /// # Panics
229    /// - If `max_denominator` is zero.
230    ///
231    /// # Examples
232    /// ```
233    /// use malachite_base::num::conversion::traits::ExactFrom;
234    /// use malachite_nz::natural::Natural;
235    /// use malachite_q::arithmetic::traits::ApproximateAssign;
236    /// use malachite_q::Rational;
237    ///
238    /// let mut x = Rational::exact_from(std::f64::consts::PI);
239    /// x.approximate_assign(&Natural::from(1000u32));
240    /// assert_eq!(x.to_string(), "355/113");
241    ///
242    /// let mut x = Rational::from_signeds(333i32, 1000);
243    /// x.approximate_assign(&Natural::from(100u32));
244    /// assert_eq!(x.to_string(), "1/3");
245    /// ```
246    fn approximate_assign(&mut self, max_denominator: &Natural) {
247        assert_ne!(*max_denominator, 0);
248        if self.denominator_ref() <= max_denominator {
249        } else if *max_denominator == 1u32 {
250            *self = Self::from(Integer::rounding_from(&*self, Nearest).0);
251        } else {
252            *self = approximate_helper(&*self, max_denominator);
253        }
254    }
255}