Here's a simple and efficient algorithm, expressed explicitly in Python. It resembles the Euclidean algorithm for computing greatest common divisors, and it can be derived from the descriptions in other answers in terms of continued fractions and/or the Stern-Brocot tree, but it's also straightforward to give a proof of correctness that doesn't use those concepts directly. We'll give the code first, and then sketch that proof of correctness.
Here's the code:
def simplest_in_interval(r, s, u, v):
Find simplest fraction in an open subinterval of the positive reals.
Given nonnegative integers r, s, u and v with r*v < s*u, return a pair
x, y of relatively prime positive integers such that x/y is the
simplest fraction in the open interval (r/s, u/v) (in the case v > 0),
or the simplest fraction in the interval (r/s, ∞) (in the case v = 0).
a, b, c, d = 1, 0, 0, 1
while s <= r or u <= v:
q = r // s
r, u, b, d = r - q * s, u - q * v, b + q * a, d + q * c
q = v // u
s, v, a, c = s - q * r, v - q * u, a + q * b, c + q * d
return a + b, c + d
A note for non-Python-speakers: the
// operator in Python performs floor division; that is,
a // b gives the value of $\lfloor a/b\rfloor$.
Here's an example of the above function in action: finding the simplest fraction in the interval
(314/100, 315/100). (Note that there's no requirement for the input fractions to be given in lowest terms.)
>>> simplest_in_interval(314, 100, 315, 100)
Sketch of proof of correctness.
Write $J$ for the originally-specified open interval $(r/s, u/v)$. As the algorithm progresses, we maintain eight separate integer-valued variables: $a$, $b$, $c$, $d$, $r$, $s$, $u$ and $v$. Now at every stage of the algorithm, the following loop invariants hold:
- $b$, $c$, $r$ and $v$ are nonnegative
- $a$, $d$, $s$ and $u$ are positive
- $rv < su$ (in fact, $su - rv$ stays constant), so $r$, $s$, $u$ and $v$ determine an open subinterval $K = (r/s, u/v)$ of the positive real line (where as a special case we allow $v=0$ to represent a right endpoint of $\infty$)
- $ad - bc = 1$, and $a$, $b$, $c$ and $d$ describe a bilinear transformation $T: x \mapsto (ax + b) / (cx + d)$
- $T$ maps $K$ bijectively (and monotonically) to the original interval $J$, so establishes an order-preserving bijection between the rationals in $K$ and those in $J$
The above invariants can be established in the usual way, by checking that they're true just before the
while loop is entered for the first time (at which point $K = J$ and $T$ is the identity map), and then by checking that if they're true at the start of any given iteration of the loop body, then they're also true at the end of the loop body for that iteration.
while loop exits, we must have $r < s$ and $v < u$, or in other words, $r/s < 1 < u/v$. So at that point the interval $K$ contains $1$. Now every fraction in the original interval $J$ has the form $(ap + bq) / (cp + dq)$ for some $p/q$ in $K$, and assuming that $p/q$ is written in lowest terms, it follows from the fact that $ad-bc = 1$ that $(ap + bq) / (cp + dq)$ is also written in lowest terms. Since $a$, $b$, $c$ and $d$ are all nonnegative, the simplest possible fraction arises when $p$ and $q$ are minimal - that is, when $p = q = 1$, and that simplest fraction is $(a + b) / (c + d)$.
There's one final thing we need to know, and that's that the
while loop does exit - that is, that the
while loop condition eventually becomes false, and that we don't simply enter an infinite loop. For this, it's enough to observe that on every iteration of the loop, either $r$ or $v$ decreases, and neither ever increases. Since both $r$ and $v$ stay nonnegative, we can't continue those decreases indefinitely, so the loop must terminate after finitely many iterations. This completes the sketch of the proof.