Odd Greedy Expansions
An Open Problem in
Number Theory
Last post I mentioned the number theory class I'm in. Dr. Dawsey had us look up open problems in number theory and pick one to explore. I picked the Odd Greedy Expansion problem.
Every positive rational number can be represented as a sum of unique unit fractions. This is called an Egyptian fraction. Fibonacci described a greedy algorithm for obtaining Egyptian fraction expansions for rationals in his Liber Abaci (1202).
It is also true that every rational number with an odd denominator can be represented as a sum of unit fractions with odd denominators, e.g. \[\frac{p}{q}=\sum_{k\in\mathbb{Z}}\frac{1}{2k+1} \] for odd \(q\). The greedy algorithm can be modified to produce only odd denominators; this is the odd greedy algorithm.
I implemented this algorithm in SML for fun. My comments explain most of what's going on, and there's a real explanation below. Just thought I'd post it in case anyone has a use for it. All the code is now on GitHub; the arbitrary-precision rational implementation is originally from here (I modified it somewhat).
fun oge (0, _) = print "0\n"
| oge (1, b) = print ("1/" ^ IntInf.toString b ^ "\n")
| oge (a, b) =
if b mod 2 = 0 then raise DenominatorNotOdd
else if a > b then raise ImproperFraction
else
(* STEPS:
* 1. u = min{ odds greater than y div x }
* i.e. if floor(y/x) is even then floor(y/x)+1 else floor(y/x)+2
* 2. include 1/u in the expansion and recurse on x/y - 1/u.
* 3. terminate when the remainder is a unit fraction
* you haven't already included.
*)
let
open rational (* Bring my arbitrary-precision rational
datatype into scope *)
infix == -- (* Allow these functions to be infix operators *)
val firstr = new (a, b) (* Construct a rational *)
fun recurse (r : rational, acc) =
(* If we're given a unit fraction with num = 1,
* & the denominator is not equal to the previous
* one, then it must be unique and it is the final
* term in the sum. We're done; reverse and return the list.
*)
if num r = 1 andalso
(den (hd acc) handle _ => IntInf.fromInt 1) <> den r
then List.rev (r :: acc)
else
let
(* The floor of the inverse, or the nearest
integer below this rational *)
val flr = IntInf.div (den r, num r)
(* The nearest odd number above this rational *)
val u = if flr mod 2 = 0 then flr + 1 else flr + 2
(* Inverse of the nearest odd integer which we
haven't used yet *)
val uinv = if null acc then
ratinv u
else if u <= den (hd acc) then
ratinv (den (hd acc) + 2)
else ratinv u
(* Difference of r and uinv, or p/q - 1/u. *)
val remaining = r -- uinv
val intgrstr = rational.show uinv
in
print ("\nSize of denom: " ^ Int.toString (size intgrstr));
print ("\nTerm: " ^ intgrstr);
(* Stick uinv into the sum and split up the
remaining amount *)
recurse (remaining, uinv :: acc)
end
(* Look ma, higher-order functions! *)
fun printlst R =
print (String.concatWith ", " (map rational.show R) ^ "\n")
in
(* Start with a/b and an empty list *)
printlst (recurse (firstr, []))
endThe algorithm is greedy because at every step it subtracts the largest odd-denominatored (that's definitely a word) unit fraction it can find. If the input is \(\frac{p}{q}\) then it subtracts the inverse of the least odd number greater than \(\frac{q}{p}\), which we haven't already used as a denominator. It repeats this process until it comes up with a unit remainder which hasn't already appeared in the expansion.
For example, \(\frac{2}{3}\) is expanded as \[ \frac{2}{3} = \frac{1}{3} + \frac{1}{5} + \frac{1}{9} + \frac{1}{45}. \]
I found an interesting expansion by iterating over a search space. It seems \(\frac{5}{139}\) has an expansion with at least 17 terms. I terminated the program after the seventeenth term, because that 17th term's denominator has 90700 digits if my code is to be believed. Here were the first few terms: \[ \frac{5}{139} = \frac{1}{29} + \frac{1}{673} + \frac{1}{387\,553} + \frac{1}{131\,422\,274\,281} + \frac{1}{15\,352\,723\,712\,926\,705\,785\,241} + \cdots \]
Sometimes these expansions blow up to enormous sizes for whatever reason, but usually they terminate in roughly four or five terms, from my cursory inspection. This is a reasonably fast and efficient program, but some particular values like \(\frac{5}{139}\) will take a while to compute.
If you play with the files, I made another function called testOverRange (p, q) which expands all fractions with numerators q and valid odd denominators equal to or less than q. Please don't plug in unreduced fractions. :)
As always, email me at ethan.barry@howdytx.technology if you have any comments!
Sources:
MathPages' old file on these problems: https://www.mathpages.com/home/kmath454.htm
The Wikipedia article: https://en.wikipedia.org/wiki/Odd_greedy_expansion
And this implementation of rationals in SML!