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}