I looked into this topic too, since i wanted a way to list all fractions in a small sub-interval of $[0,1]$. If you just want to look at an implementation that does this I attached some typescript code. My approach came from looking into the Farey sequence $\cal F_n$. One can show that if $a/b$ and $c/d$ are fractions such that no other fraction $e/f$ with $a/b < e/f < c/d$ and $f \leq \max(b,d)$ exists, then
$$ \frac{a+c}{b+d} $$
is the fration with the smallest denominator between $a/b$ and $c/d$.
For our case we choose a number $v \in (0,1)$ that we want to approximate.
Formulating this as an algorithm we can start with the lower bound $a / b = 0/1$ and upper bound $c/d = 1/1$. Next we define
$$ \frac{e}{f} = \frac{a+c}{b+d}. $$
Now 3 possible cases can happen. If $f$ is too large, the best approximation for $v$ is either $a/b$ or $c/d$. Otherwise we have have
$e/f < v$ or $e/f \geq v$. If $e/f < v$ replace $a/b$ by $e/f$ and repeat the steps. In the other case replace $c/d$ by $e/f$. More formally:
Choose: A value $v$ to approximate and the maximum denominator $Q$.
Initialize: $a/b := 0/1$ and $c/d := 1/1$.
Iterate: for $a/b < v \leq c/d$:
- Evaluate: $e/f := (a+c)/(b+d)$.
- If $f \geq Q$: Return $a/b$ or $c/d$.
- If $e/f < v$: Replace $a/b := e/f$ and begin next iteration.
- If $v \leq e/f$: Replace $c/d := e/f and begin next iteration.
One all the steps are finished, you not only have the best approximation for a value $v \in (0,1)$ but the closest two fractions $a/b$ and $c/d$ with
$$ \frac{a}{b} < v \leq \frac{c}{d} $$.
This algorithm is what I started out with. It has worst case complexity $O(Q)$ time and $O(1)$ space. One can improve the algorithm by batching up multiple steps. I did not prove the complexity, but I think the improved version has complexity $O(\log Q)$ time and $O(1)$ memory. I will attach some Typescript code for this improved algorithm.
/**
* Returns the two fractions closest to $v$ with $a/b <= v <= c/d$. One always has $a/b \neq c/d$.
*
* # The Algorithm:
*
* Chooses $\frac{a^+}{b^+} = \frac{a + e_1 c}{b + e_1 d} <= v$ with $b + e_1 d <= Q$ in the first,
* and $\frac{c^+}{d^+} = \frac{e_2 a^+ + c}{ e_2 b^+ + d} >= v$ with $e_2 b + d <= Q$ in the second step.
* Iterate until nothing happens anymore.
*
* If $0 < v < 1$, the final fractions will be consecutive elements in the Farey sequence $F_Q$.
*/
function findLowerAndUpperApproximation(v: number, Q: number) {
// Ensure v in [0,1)
let v_int = Math.floor(v);
v = v - v_int; // only use fractional part of $v$ for iteration
let a = 0,
b = 1,
c = 1,
d = 1;
while (true) {
let _tmp = c - v * d; // Temporary denominator for 0 check
// Batch e1 steps in direction c/d
let e1: number | undefined;
if (_tmp != 0) e1 = Math.floor((v * b - a) / _tmp);
if (e1 === undefined || b + e1 * d > Q) e1 = Math.floor((Q - b) / d);
a = a + e1 * c;
b = b + e1 * d;
_tmp = b * v - a; // Temporary denominator for 0 check
// Batch e2 steps in direction a/b
let e2: number | undefined;
if (_tmp != 0) e2 = Math.floor((c - v * d) / _tmp);
if (e2 === undefined || b * e2 + d > Q) e2 = Math.floor((Q - d) / b);
c = a * e2 + c;
d = b * e2 + d;
// If both steps do nothing we are done
if (e1 == 0 && e2 == 0) break;
}
return [v_int*b + a, b, v_int*d + c, d];
}
In each step the denominator of $c/d$ should at least double. This would result in complexity $O(\log Q)$. However, i haven't worked out the details here. For me the algorithm is good enough to find the best fractions for $Q = 10^9$. Going past that my code seems to hit machine precision problems, which most likely affect the divisions in the algorithm. When you use a language with higher precision integer division you should be fine to go almost as far as you want.