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}