malachite_q/arithmetic/denominators_in_closed_interval.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::{DenominatorsInClosedInterval, SimplestRationalInInterval};
11use crate::exhaustive::{
12 exhaustive_rationals_with_denominator_inclusive_range,
13 exhaustive_rationals_with_denominator_range,
14};
15use alloc::collections::BTreeSet;
16use malachite_base::num::arithmetic::traits::{Ceiling, Reciprocal, UnsignedAbs};
17use malachite_base::num::basic::traits::{One, Two, Zero};
18use malachite_base::num::factorization::traits::Primes;
19use malachite_nz::natural::Natural;
20use malachite_nz::platform::Limb;
21
22// Returns a k such that for all n >= k, any closed interval with the given diameter is guaranteed
23// to contain rationals with (reduced) denominator n.
24fn smallest_guaranteed_denominator(interval_diameter: &Rational) -> Natural {
25 if *interval_diameter >= 1u32 {
26 return Natural::ONE;
27 }
28 let mut primorial = Natural::TWO;
29 let mut pow = Natural::TWO;
30 for p in Limb::primes().skip(1) {
31 primorial *= Natural::from(p);
32 pow <<= 1;
33 let limit = Rational::from_naturals_ref(&pow, &primorial);
34 if *interval_diameter >= limit {
35 return primorial;
36 }
37 }
38 panic!();
39}
40
41fn smallest_likely_denominator(interval_diameter: &Rational) -> Natural {
42 interval_diameter.reciprocal().ceiling().unsigned_abs()
43}
44
45/// Returns an iterator of all denominators that appear in the [`Rational`]s contained in a closed
46/// interval.
47///
48/// This `struct` is created by [`DenominatorsInClosedInterval::denominators_in_closed_interval`];
49/// see its documentation for more.
50#[derive(Clone, Debug)]
51pub struct DenominatorsInClosedRationalInterval {
52 a: Rational,
53 b: Rational,
54 low_threshold: Natural,
55 high_threshold: Natural,
56 current: Natural,
57 points: BTreeSet<Rational>,
58}
59
60impl Iterator for DenominatorsInClosedRationalInterval {
61 type Item = Natural;
62
63 fn next(&mut self) -> Option<Natural> {
64 if self.current >= self.high_threshold {
65 self.points.clear();
66 self.current += Natural::ONE;
67 Some(self.current.clone())
68 } else if self.current >= self.low_threshold {
69 self.points.clear();
70 loop {
71 self.current += Natural::ONE;
72 if exhaustive_rationals_with_denominator_inclusive_range(
73 self.current.clone(),
74 self.a.clone(),
75 self.b.clone(),
76 )
77 .next()
78 .is_some()
79 {
80 return Some(self.current.clone());
81 }
82 }
83 } else if self.points.is_empty() {
84 assert_eq!(self.current, 0u32);
85 self.points.insert(self.a.clone());
86 self.points.insert(self.b.clone());
87 self.points
88 .insert(Rational::simplest_rational_in_open_interval(
89 &self.a, &self.b,
90 ));
91 let mut min_denominator = self.a.denominator_ref();
92 for p in &self.points {
93 let pd = p.denominator_ref();
94 if pd < min_denominator {
95 min_denominator = pd;
96 }
97 }
98 self.current = min_denominator.clone();
99 for p in exhaustive_rationals_with_denominator_range(
100 self.current.clone(),
101 self.a.clone(),
102 self.b.clone(),
103 ) {
104 self.points.insert(p);
105 }
106 Some(self.current.clone())
107 } else {
108 let mut previous_point = None;
109 let mut min_interior_denominator = None;
110 for p in &self.points {
111 if let Some(previous) = previous_point {
112 let interior_denominator =
113 Rational::simplest_rational_in_open_interval(previous, p)
114 .into_denominator();
115 if let Some(previous_min) = min_interior_denominator.as_ref() {
116 if interior_denominator < *previous_min {
117 min_interior_denominator = Some(interior_denominator);
118 }
119 } else {
120 min_interior_denominator = Some(interior_denominator);
121 }
122 }
123 previous_point = Some(p);
124 }
125 let min_interior_denominator = min_interior_denominator.unwrap();
126 assert!(min_interior_denominator > self.current);
127 let mut min_denominator = min_interior_denominator;
128 let ad = self.a.denominator_ref();
129 if *ad > self.current && *ad < min_denominator {
130 min_denominator = ad.clone();
131 }
132 let bd = self.b.denominator_ref();
133 if *bd > self.current && *bd < min_denominator {
134 min_denominator = bd.clone();
135 }
136 self.current = min_denominator;
137 for p in exhaustive_rationals_with_denominator_range(
138 self.current.clone(),
139 self.a.clone(),
140 self.b.clone(),
141 ) {
142 self.points.insert(p);
143 }
144 Some(self.current.clone())
145 }
146 }
147}
148
149impl DenominatorsInClosedInterval for Rational {
150 type Denominators = DenominatorsInClosedRationalInterval;
151
152 /// Returns an iterator of all denominators that appear in the [`Rational`]s contained in a
153 /// closed interval.
154 ///
155 /// For example, consider the interval $[1/3, 1/2]$. It contains no integers, so no
156 /// [`Rational`]s with denominator 1. It does contain [`Rational`]s with denominators 2 and 3
157 /// (the endpoints). It contains none with denominator 4, but it does contain $2/5$. It contains
158 /// none with denominator 6 (though $1/3$ and $1/2$ are $2/6$ and $3/6$, those representations
159 /// are not reduced). It contains $3/7$, $3/8$, and $4/9$ but none with denominator 10 ($0.4$
160 /// does not count because it is $2/5$). It contains all denominators greater than 10, so the
161 /// complete list is $2, 3, 5, 7, 8, 9, 11, 12, 13, \ldots$.
162 ///
163 /// # Worst-case complexity per iteration
164 /// $T(n, i) = O(n + \log i)$
165 ///
166 /// $M(n, i) = O(n + \log i)$
167 ///
168 /// where $T$ is time, $M$ is additional memory, $i$ is the iteration number, and $n$ is
169 /// `max(a.significant_bits(), b.significant_bits())`.
170 ///
171 /// # Panics
172 /// Panics if $a \geq b$.
173 ///
174 /// ```
175 /// use malachite_base::iterators::prefix_to_string;
176 /// use malachite_base::num::basic::traits::{One, Two};
177 /// use malachite_q::arithmetic::traits::DenominatorsInClosedInterval;
178 /// use malachite_q::Rational;
179 ///
180 /// assert_eq!(
181 /// prefix_to_string(
182 /// Rational::denominators_in_closed_interval(Rational::ONE, Rational::TWO),
183 /// 20
184 /// ),
185 /// "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...]"
186 /// );
187 /// assert_eq!(
188 /// prefix_to_string(
189 /// Rational::denominators_in_closed_interval(
190 /// Rational::from_signeds(1, 3),
191 /// Rational::from_signeds(1, 2)
192 /// ),
193 /// 20
194 /// ),
195 /// "[2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, ...]"
196 /// );
197 /// assert_eq!(
198 /// prefix_to_string(
199 /// Rational::denominators_in_closed_interval(
200 /// Rational::from_signeds(1, 1000001),
201 /// Rational::from_signeds(1, 1000000)
202 /// ),
203 /// 20
204 /// ),
205 /// "[1000000, 1000001, 3000001, 3000002, 4000001, 4000003, 5000001, 5000002, 5000003, \
206 /// 5000004, 6000001, 6000005, 7000001, 7000002, 7000003, 7000004, 7000005, 7000006, \
207 /// 8000001, 8000003, ...]"
208 /// );
209 /// ```
210 fn denominators_in_closed_interval(
211 a: Rational,
212 b: Rational,
213 ) -> DenominatorsInClosedRationalInterval {
214 assert!(a < b);
215 let diameter = &b - &a;
216 let (mut low_threshold, high_threshold) = if diameter >= 1u32 {
217 (Natural::ZERO, Natural::ZERO)
218 } else {
219 (
220 smallest_likely_denominator(&diameter),
221 smallest_guaranteed_denominator(&diameter),
222 )
223 };
224 if low_threshold < 100u32 {
225 low_threshold = Natural::ZERO;
226 }
227 DenominatorsInClosedRationalInterval {
228 a,
229 b,
230 low_threshold,
231 high_threshold,
232 current: Natural::ZERO,
233 points: BTreeSet::new(),
234 }
235 }
236}