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 for p in &self.points {
129 let pd = p.denominator_ref();
130 if *pd > self.current && *pd < min_denominator {
131 min_denominator = pd.clone();
132 }
133 }
134 self.current = min_denominator;
135 for p in exhaustive_rationals_with_denominator_range(
136 self.current.clone(),
137 self.a.clone(),
138 self.b.clone(),
139 ) {
140 self.points.insert(p);
141 }
142 Some(self.current.clone())
143 }
144 }
145}
146
147impl DenominatorsInClosedInterval for Rational {
148 type Denominators = DenominatorsInClosedRationalInterval;
149
150 /// Returns an iterator of all denominators that appear in the [`Rational`]s contained in a
151 /// closed interval.
152 ///
153 /// For example, consider the interval $[1/3, 1/2]$. It contains no integers, so no
154 /// [`Rational`]s with denominator 1. It does contain [`Rational`]s with denominators 2 and 3
155 /// (the endpoints). It contains none with denominator 4, but it does contain $2/5$. It contains
156 /// none with denominator 6 (though $1/3$ and $1/2$ are $2/6$ and $3/6$, those representations
157 /// are not reduced). It contains $3/7$, $3/8$, and $4/9$ but none with denominator 10 ($0.4$
158 /// does not count because it is $2/5$). It contains all denominators greater than 10, so the
159 /// complete list is $2, 3, 5, 7, 8, 9, 11, 12, 13, \ldots$.
160 ///
161 /// # Worst-case complexity per iteration
162 /// $T(n, i) = O(n + \log i)$
163 ///
164 /// $M(n, i) = O(n + \log i)$
165 ///
166 /// where $T$ is time, $M$ is additional memory, $i$ is the iteration number, and $n$ is
167 /// `max(a.significant_bits(), b.significant_bits())`.
168 ///
169 /// # Panics
170 /// Panics if $a \geq b$.
171 ///
172 /// ```
173 /// use malachite_base::iterators::prefix_to_string;
174 /// use malachite_base::num::basic::traits::{One, Two};
175 /// use malachite_q::arithmetic::traits::DenominatorsInClosedInterval;
176 /// use malachite_q::Rational;
177 ///
178 /// assert_eq!(
179 /// prefix_to_string(
180 /// Rational::denominators_in_closed_interval(Rational::ONE, Rational::TWO),
181 /// 20
182 /// ),
183 /// "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...]"
184 /// );
185 /// assert_eq!(
186 /// prefix_to_string(
187 /// Rational::denominators_in_closed_interval(
188 /// Rational::from_signeds(1, 3),
189 /// Rational::from_signeds(1, 2)
190 /// ),
191 /// 20
192 /// ),
193 /// "[2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, ...]"
194 /// );
195 /// assert_eq!(
196 /// prefix_to_string(
197 /// Rational::denominators_in_closed_interval(
198 /// Rational::from_signeds(1, 1000001),
199 /// Rational::from_signeds(1, 1000000)
200 /// ),
201 /// 20
202 /// ),
203 /// "[1000000, 1000001, 2000001, 3000001, 3000002, 4000001, 4000003, 5000001, 5000002, \
204 /// 5000003, 5000004, 6000001, 6000005, 7000001, 7000002, 7000003, 7000004, 7000005, \
205 /// 7000006, 8000001, ...]"
206 /// );
207 /// ```
208 fn denominators_in_closed_interval(
209 a: Rational,
210 b: Rational,
211 ) -> DenominatorsInClosedRationalInterval {
212 assert!(a < b);
213 let diameter = &b - &a;
214 let (mut low_threshold, high_threshold) = if diameter >= 1u32 {
215 (Natural::ZERO, Natural::ZERO)
216 } else {
217 (
218 smallest_likely_denominator(&diameter),
219 smallest_guaranteed_denominator(&diameter),
220 )
221 };
222 if low_threshold < 100u32 {
223 low_threshold = Natural::ZERO;
224 }
225 DenominatorsInClosedRationalInterval {
226 a,
227 b,
228 low_threshold,
229 high_threshold,
230 current: Natural::ZERO,
231 points: BTreeSet::new(),
232 }
233 }
234}