{
  "id": "P067",
  "slug": "fraction-tutor",
  "key": "P067-fraction-tutor",
  "title": "Fraction tutor",
  "summary": "A fraction put in lowest terms with every step of Euclid's algorithm shown, its decimal with the repeating part found by long division, a repeating decimal like 0.1(6) turned back into a fraction, the continued fraction with its convergents, and Egyptian fractions by Fibonacci's greedy rule.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P067 - Fraction tutor\n\nA fraction put in lowest terms, with every step of Euclid's algorithm\nshown. Its decimal, with the repeating part found by long division. A\nrepeating decimal like 0.1(6) turned back into a fraction. The continued\nfraction with its convergents, and an Egyptian fraction - a sum of\ndifferent unit fractions - by Fibonacci's greedy rule. All the arithmetic\nis on whole numbers, so every answer is exact.\n\n- `main.eml` - the menu, reading fractions and decimals, and the messages\n- `rational.eml` - exact division, Euclid's algorithm, decimals both ways,\n  continued fractions with their convergents, and Egyptian fractions\n\nHow each part works:\n\n- Lowest terms: as in the corpus case `euclidean-gcd-recursive`,\n  gcd(a, b) = gcd(b, a mod b) until the remainder is 0, and each step\n  a = q x b + r is printed. Top and bottom are divided by the last\n  remainder that was not 0. The corpus case `fraction-arithmetic` reduces\n  its fractions the same way after every operation.\n- Division is exact long division by doubling, never a float: a / b goes\n  through a float, which rounds once numbers pass 2^53.\n- To a decimal: long division that remembers each remainder and the digit\n  it came at. When a remainder comes back, the digits from that point on\n  repeat: 7/12 = 0.58(3). The division ends only if a remainder reaches 0,\n  and that happens exactly when the reduced denominator has no prime\n  factor but 2 and 5.\n- From a decimal: 1.2(45) is 12/10 plus 45/990 (the repeating digits over\n  as many 9s, shifted past the fixed digits), reduced to 137/110. 0.(9)\n  comes back as 1.\n- Continued fraction: take the whole part (rounded down, so -7/3 starts at\n  -3), turn what is left upside down, and repeat - the same divisions as\n  Euclid's algorithm. Convergents come from p = a p' + p'', q = a q' + q''.\n  103993/33102 gives 3, 22/7, 333/106 and 355/113, the convergents of pi.\n  Each convergent is closer than any fraction with a smaller denominator.\n  From the second on, each is also the closest fraction with a denominator\n  no larger than its own; the first need not be: -7/3 starts at -3, though\n  -2 is closer.\n- Egyptian fraction: the greedy rule takes the largest unit fraction that\n  fits, 1/ceil(d/n), and repeats on what is left. The numerator left over\n  is smaller every time, so the rule always ends, but the denominators can\n  grow fast: 5/121 ends with a 25-digit one. The program stops at 12 terms\n  and says so.\n\nWhat is checked: menu choices 1 to 6; a fraction as a/b or a whole number,\nwith a minus sign allowed, up to 12 digits on each side and a bottom that\nis not 0; a decimal as digits, a point if there is a fraction part, and\nrepeating digits in brackets after it, up to 1000 digits; for an Egyptian\nfraction, a value strictly between 0 and 1. An empty answer cancels.\n\nSessions: `sessions/basic.in` gives:\n- 1071/462 = 51/22, through three steps of Euclid's algorithm;\n- the decimals of 1/7, 7/12 and 3/8 - repeating 6 at a time, repeating\n  after a fixed part, and ending;\n- 0.1(6) = 1/6 and 1.2(45) = 137/110;\n- the continued fractions of 415/93 and 103993/33102;\n- 4/13 = 1/4 + 1/18 + 1/468, and the five greedy terms of 5/121.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- the fractions abc, 1/0, 1/2/3 and a 13-digit top, then a cancel;\n- 0/5, and the decimal of -22/7;\n- the decimals abc, .5 and \"1.(\", then a cancel;\n- the continued fraction of -7/3, starting below at -3;\n- 3/2 and -1/2 refused for an Egyptian fraction.\n\nBuilt on the verified corpus cases `euclidean-gcd-recursive` (Euclid's\nalgorithm by recursion) and `fraction-arithmetic` (fractions held as\n[numerator, denominator] pairs, reduced by their greatest common divisor).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P067 fraction tutor: a fraction in lowest terms with Euclid's steps shown,\n# its decimal with the repeating part found, a decimal - repeating or not -\n# turned back into a fraction, the continued fraction with its convergents,\n# and Egyptian fractions.\nimport rational\n\ndef trim(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef whole(s):\n    # A whole number, maybe negative, of up to 12 digits, as [ok, value].\n    s => digits\n    1 => sg\n    if len(s) > 1 and s[0] == \"-\":\n        s[1:len(s)] => digits\n        -1 => sg\n    if digits == \"\" or len(digits) > 12:\n        return [False, 0]\n    for c in digits:\n        if not (c >= \"0\" and c <= \"9\"):\n            return [False, 0]\n    return [True, sg * int(digits)]\n\ndef ask_fraction(prompt):\n    # [n, d] with d != 0 from \"a/b\" or a whole number, or [] when empty.\n    while True:\n        trim(input(prompt)) => s\n        if s == \"\":\n            return []\n        0 => k\n        while k < len(s) and s[k] != \"/\":\n            k + 1 => k\n        whole(trim(s[0:k])) => n\n        [True, 1] => d\n        if k < len(s):\n            whole(trim(s[k + 1:len(s)])) => d\n        if n[0] and d[0] and d[1] != 0:\n            return [n[1], d[1]]\n        \"Type a fraction like 1071/462 or -3/4, or a whole number - up to 12 digits each, and the bottom not 0.\" ^0\n\ndef text(f):\n    if f[1] == 1:\n        return str(f[0])\n    return str(f[0]) + \"/\" + str(f[1])\n\ndef simplify():\n    ask_fraction(\"fraction> \") => f\n    if len(f) == 0:\n        \"Cancelled.\" ^0\n        return\n    rational.reduced(f[0], f[1]) => r\n    for st in r[1]:\n        (\"  \" + str(st[0]) + \" = \" + str(st[2]) + \" x \" + str(st[1]) + \" + \" + str(st[3])) ^0\n    if f[0] == 0:\n        (\"0/\" + str(abs(f[1])) + \" is 0.\") ^0\n        return\n    (\"The greatest common divisor is \" + str(r[2]) + \", so \" + text(f) + \" = \" + text(r[0]) + \".\") ^0\n\ndef to_decimal():\n    ask_fraction(\"fraction> \") => f\n    if len(f) == 0:\n        \"Cancelled.\" ^0\n        return\n    rational.reduced(f[0], f[1])[0] => r\n    rational.decimal(r[0], r[1]) => parts\n    (text(r) + \" = \" + rational.decimal_text(parts)) ^0\n    if len(parts[2]) == 1:\n        (\"The digit \" + parts[2] + \" repeats for ever.\") ^0\n    elif parts[2] != \"\":\n        (\"The digits \" + parts[2] + \" repeat for ever, \" + str(len(parts[2])) + \" at a time.\") ^0\n    else:\n        \"The decimal ends: the denominator has no prime factor but 2 and 5.\" ^0\n\ndef from_decimal():\n    while True:\n        trim(input(\"decimal (repeating digits in brackets, like 0.1(6))> \")) => s\n        if s == \"\":\n            \"Cancelled.\" ^0\n            return\n        rational.from_decimal(s) => f\n        if len(f) > 0:\n            (s + \" = \" + text(f)) ^0\n            return\n        \"Type digits, with a point if there is a fraction part and any repeating digits in brackets after it - like 3, 2.5, 0.(3) or 1.2(45).\" ^0\n\ndef continued():\n    ask_fraction(\"fraction> \") => f\n    if len(f) == 0:\n        \"Cancelled.\" ^0\n        return\n    rational.reduced(f[0], f[1])[0] => r\n    rational.continued(r[0], r[1]) => terms\n    \"[\" => s\n    for k in [0:len(terms) - 1]:\n        if k == 1:\n            s + \"; \" => s\n        elif k > 1:\n            s + \", \" => s\n        s + str(terms[k]) => s\n    (text(r) + \" = \" + s + \"]\") ^0\n    \"Convergents - each closer than any fraction with a smaller denominator:\" ^0\n    \"\" => line\n    for c in rational.convergents(terms):\n        if line != \"\":\n            line + \", \" => line\n        line + text(c) => line\n    (\"  \" + line) ^0\n\ndef egyptian():\n    ask_fraction(\"fraction between 0 and 1> \") => f\n    if len(f) == 0:\n        \"Cancelled.\" ^0\n        return\n    rational.reduced(f[0], f[1])[0] => r\n    if r[0] <= 0 or r[0] >= r[1]:\n        \"Egyptian fractions here are for a fraction strictly between 0 and 1.\" ^0\n        return\n    rational.egyptian(r[0], r[1]) => e\n    \"\" => s\n    for k in e[0]:\n        if s != \"\":\n            s + \" + \" => s\n        s + \"1/\" + str(k) => s\n    if e[1]:\n        (text(r) + \" = \" + s) ^0\n    else:\n        (text(r) + \" = \" + s + \" + ... - the greedy rule has gone 12 terms and the denominators keep growing.\") ^0\n\n\"== Fraction tutor ==\" ^0\n\"Fractions in lowest terms, their decimals, continued fractions and Egyptian fractions.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) lowest terms  2) to a decimal  3) from a decimal  4) continued fraction  5) Egyptian fraction  6) quit\" ^0\n    trim(input(\"choice> \")) => choice\n    if choice == \"1\":\n        simplify()\n    elif choice == \"2\":\n        to_decimal()\n    elif choice == \"3\":\n        from_decimal()\n    elif choice == \"4\":\n        continued()\n    elif choice == \"5\":\n        egyptian()\n    elif choice == \"6\":\n        False => running\n    else:\n        \"Pick a number from 1 to 6.\" ^0\n\"Bye.\" ^0\n",
      "python": "import rational\n\ndef trim(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef whole(s):\n    digits = s\n    sg = 1\n    if len(s) > 1 and s[0] == \"-\":\n        digits = s[1:len(s)]\n        sg = -1\n    if digits == \"\" or len(digits) > 12:\n        return [False, 0]\n    for c in digits:\n        if not (c >= \"0\" and c <= \"9\"):\n            return [False, 0]\n    return [True, sg * int(digits)]\n\ndef ask_fraction(prompt):\n    while True:\n        s = trim(input(prompt))\n        if s == \"\":\n            return []\n        k = 0\n        while k < len(s) and s[k] != \"/\":\n            k = k + 1\n        n = whole(trim(s[0:k]))\n        d = [True, 1]\n        if k < len(s):\n            d = whole(trim(s[k + 1:len(s)]))\n        if n[0] and d[0] and d[1] != 0:\n            return [n[1], d[1]]\n        print(\"Type a fraction like 1071/462 or -3/4, or a whole number - up to 12 digits each, and the bottom not 0.\")\n\ndef text(f):\n    if f[1] == 1:\n        return str(f[0])\n    return str(f[0]) + \"/\" + str(f[1])\n\ndef simplify():\n    f = ask_fraction(\"fraction> \")\n    if len(f) == 0:\n        print(\"Cancelled.\")\n        return\n    r = rational.reduced(f[0], f[1])\n    for st in r[1]:\n        print(\"  \" + str(st[0]) + \" = \" + str(st[2]) + \" x \" + str(st[1]) + \" + \" + str(st[3]))\n    if f[0] == 0:\n        print(\"0/\" + str(abs(f[1])) + \" is 0.\")\n        return\n    print(\"The greatest common divisor is \" + str(r[2]) + \", so \" + text(f) + \" = \" + text(r[0]) + \".\")\n\ndef to_decimal():\n    f = ask_fraction(\"fraction> \")\n    if len(f) == 0:\n        print(\"Cancelled.\")\n        return\n    r = rational.reduced(f[0], f[1])[0]\n    parts = rational.decimal(r[0], r[1])\n    print(text(r) + \" = \" + rational.decimal_text(parts))\n    if len(parts[2]) == 1:\n        print(\"The digit \" + parts[2] + \" repeats for ever.\")\n    elif parts[2] != \"\":\n        print(\"The digits \" + parts[2] + \" repeat for ever, \" + str(len(parts[2])) + \" at a time.\")\n    else:\n        print(\"The decimal ends: the denominator has no prime factor but 2 and 5.\")\n\ndef from_decimal():\n    while True:\n        s = trim(input(\"decimal (repeating digits in brackets, like 0.1(6))> \"))\n        if s == \"\":\n            print(\"Cancelled.\")\n            return\n        f = rational.from_decimal(s)\n        if len(f) > 0:\n            print(s + \" = \" + text(f))\n            return\n        print(\"Type digits, with a point if there is a fraction part and any repeating digits in brackets after it - like 3, 2.5, 0.(3) or 1.2(45).\")\n\ndef continued():\n    f = ask_fraction(\"fraction> \")\n    if len(f) == 0:\n        print(\"Cancelled.\")\n        return\n    r = rational.reduced(f[0], f[1])[0]\n    terms = rational.continued(r[0], r[1])\n    s = \"[\"\n    for k in range(0, len(terms)):\n        if k == 1:\n            s = s + \"; \"\n        elif k > 1:\n            s = s + \", \"\n        s = s + str(terms[k])\n    print(text(r) + \" = \" + s + \"]\")\n    print(\"Convergents - each closer than any fraction with a smaller denominator:\")\n    line = \"\"\n    for c in rational.convergents(terms):\n        if line != \"\":\n            line = line + \", \"\n        line = line + text(c)\n    print(\"  \" + line)\n\ndef egyptian():\n    f = ask_fraction(\"fraction between 0 and 1> \")\n    if len(f) == 0:\n        print(\"Cancelled.\")\n        return\n    r = rational.reduced(f[0], f[1])[0]\n    if r[0] <= 0 or r[0] >= r[1]:\n        print(\"Egyptian fractions here are for a fraction strictly between 0 and 1.\")\n        return\n    e = rational.egyptian(r[0], r[1])\n    s = \"\"\n    for k in e[0]:\n        if s != \"\":\n            s = s + \" + \"\n        s = s + \"1/\" + str(k)\n    if e[1]:\n        print(text(r) + \" = \" + s)\n    else:\n        print(text(r) + \" = \" + s + \" + ... - the greedy rule has gone 12 terms and the denominators keep growing.\")\n\nprint(\"== Fraction tutor ==\")\nprint(\"Fractions in lowest terms, their decimals, continued fractions and Egyptian fractions.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) lowest terms  2) to a decimal  3) from a decimal  4) continued fraction  5) Egyptian fraction  6) quit\")\n    choice = trim(input(\"choice> \"))\n    if choice == \"1\":\n        simplify()\n    elif choice == \"2\":\n        to_decimal()\n    elif choice == \"3\":\n        from_decimal()\n    elif choice == \"4\":\n        continued()\n    elif choice == \"5\":\n        egyptian()\n    elif choice == \"6\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 6.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "rational.eml",
      "eml": "# P067 fraction tutor - Euclid's steps, decimals with their repeating part,\n# continued fractions and Egyptian fractions. Fractions are [n, d] with\n# d > 0. Egyptian fractions can reach denominators far past what a float\n# holds, so whole-number division is written out exactly.\n\ndef div(a, b):\n    # a // b for a >= 0 and b > 0, exact at any size: long division by\n    # doubling, taking away the largest b * 2^k that still fits.\n    0 => q\n    while a >= b:\n        b => m\n        1 => k\n        while m + m <= a:\n            m + m => m\n            k + k => k\n        a - m => a\n        q + k => q\n    return q\n\ndef floor_div(n, d):\n    # n // d for any n and d > 0: rounding down, also below zero.\n    if n >= 0:\n        return div(n, d)\n    return 0 - div(0 - n + d - 1, d)\n\ndef euclid(a, b, steps):\n    # The corpus case euclidean-gcd-recursive: gcd(a, b) = gcd(b, a mod b),\n    # and gcd(a, 0) = a. Each step [a, b, quotient, remainder] is added to\n    # steps (a one-item list holding the list, so every call adds to it).\n    if b == 0:\n        return a\n    steps[0] + [[a, b, div(a, b), a % b]] => steps[0]\n    return euclid(b, a % b, steps)\n\ndef reduced(n, d):\n    # [n, d] in lowest terms with d > 0, and the Euclid steps that found the\n    # divisor.\n    [[]] => steps\n    euclid(abs(n), abs(d), steps) => g\n    if g == 0:\n        1 => g\n    if d < 0:\n        0 - n => n\n        0 - d => d\n    return [[div(abs(n), g) * sign(n), div(d, g)], steps[0], g]\n\ndef sign(n):\n    if n < 0:\n        return -1\n    return 1\n\ndef decimal(n, d):\n    # The decimal of n/d (d > 0) as [whole part text, digits before the\n    # repeat, repeating digits]: long division, remembering at which digit\n    # each remainder first came - when one comes again, the digits since\n    # then repeat for ever. A remainder of 0 means the decimal ends.\n    \"\" => minus\n    if n < 0:\n        \"-\" => minus\n    abs(n) => a\n    str(div(a, d)) => whole\n    a % d => r\n    {} => seen\n    \"\" => digits\n    while r != 0 and not (r in seen):\n        len(digits) => seen[r]\n        r * 10 => r\n        digits + str(div(r, d)) => digits\n        r % d => r\n    if r == 0:\n        return [minus + whole, digits, \"\"]\n    seen[r] => start\n    return [minus + whole, digits[0:start], digits[start:len(digits)]]\n\ndef decimal_text(parts):\n    parts[0] => s\n    if parts[1] != \"\" or parts[2] != \"\":\n        s + \".\" + parts[1] => s\n        if parts[2] != \"\":\n            s + \"(\" + parts[2] + \")\" => s\n    return s\n\ndef from_decimal(s):\n    # \"0.125\", \"-2.5\", \"0.(3)\", \"0.1(6)\", \"3\" as an exact fraction [n, d], or\n    # []. The repeating part is in brackets: 0.1(6) = 1/6.\n    1 => sg\n    if len(s) > 0 and s[0] == \"-\":\n        -1 => sg\n        s[1:len(s)] => s\n    \"\" => whole\n    \"\" => fixed\n    \"\" => rep\n    0 => state\n    for ch in s:\n        if ch >= \"0\" and ch <= \"9\":\n            if state == 0:\n                whole + ch => whole\n            elif state == 1:\n                fixed + ch => fixed\n            elif state == 2:\n                rep + ch => rep\n            else:\n                return []\n        elif ch == \".\" and state == 0:\n            1 => state\n        elif ch == \"(\" and state == 1:\n            2 => state\n        elif ch == \")\" and state == 2 and rep != \"\":\n            3 => state\n        else:\n            return []\n    # Whole numbers have no size limit, so long repeats (1/97 has 96 digits)\n    # read fine; 1000 digits is a guard against nonsense input.\n    if whole == \"\" or state == 2 or len(whole) + len(fixed) + len(rep) > 1000:\n        return []\n    # x = whole.fixed(rep): with k fixed digits and m repeating ones,\n    # x = (whole fixed rep - whole fixed) / (10^k (10^m - 1)); with no\n    # repeat, x = whole fixed / 10^k.\n    1 => tk\n    for c in fixed:\n        tk * 10 => tk\n    int(whole + fixed) => wf\n    if rep == \"\":\n        return reduced(sg * wf, tk)[0]\n    1 => tm\n    for c in rep:\n        tm * 10 => tm\n    int(whole + fixed + rep) => wfr\n    return reduced(sg * (wfr - wf), tk * (tm - 1))[0]\n\ndef continued(n, d):\n    # [a0; a1, a2, ...] for n/d by the same divisions as Euclid's algorithm:\n    # each quotient is the next term, rounding down (so a0 can be negative).\n    [] => terms\n    while d != 0:\n        floor_div(n, d) => a\n        terms + [a] => terms\n        n - a * d => r\n        d => n\n        r => d\n    return terms\n\ndef convergents(terms):\n    # p_k / q_k from p_k = a_k p_(k-1) + p_(k-2), likewise q - each the best\n    # fraction with a denominator that small.\n    [] => out\n    1 => p1\n    0 => p2\n    0 => q1\n    1 => q2\n    for a in terms:\n        a * p1 + p2 => p\n        a * q1 + q2 => q\n        out + [[p, q]] => out\n        p1 => p2\n        p => p1\n        q1 => q2\n        q => q1\n    return out\n\ndef egyptian(n, d):\n    # 0 < n/d < 1 as a sum of different unit fractions by Fibonacci's greedy\n    # rule: take the largest 1/k not above what is left, and repeat. It\n    # always ends, since each step leaves a smaller numerator. Stops after\n    # 12 terms (returning [] for the rest) - greedy denominators can grow\n    # very fast.\n    [] => out\n    while n > 0:\n        if len(out) == 12:\n            return [out, False]\n        div(d + n - 1, n) => k\n        out + [k] => out\n        n * k - d => n\n        d * k => d\n        reduced(n, d)[0] => f\n        f[0] => n\n        f[1] => d\n    return [out, True]\n",
      "python": "def div(a, b):\n    q = 0\n    while a >= b:\n        m = b\n        k = 1\n        while m + m <= a:\n            m = m + m\n            k = k + k\n        a = a - m\n        q = q + k\n    return q\n\ndef floor_div(n, d):\n    if n >= 0:\n        return div(n, d)\n    return 0 - div(0 - n + d - 1, d)\n\ndef euclid(a, b, steps):\n    if b == 0:\n        return a\n    steps[0] = steps[0] + [[a, b, div(a, b), a % b]]\n    return euclid(b, a % b, steps)\n\ndef reduced(n, d):\n    steps = [[]]\n    g = euclid(abs(n), abs(d), steps)\n    if g == 0:\n        g = 1\n    if d < 0:\n        n = 0 - n\n        d = 0 - d\n    return [[div(abs(n), g) * sign(n), div(d, g)], steps[0], g]\n\ndef sign(n):\n    if n < 0:\n        return -1\n    return 1\n\ndef decimal(n, d):\n    minus = \"\"\n    if n < 0:\n        minus = \"-\"\n    a = abs(n)\n    whole = str(div(a, d))\n    r = a % d\n    seen = {}\n    digits = \"\"\n    while r != 0 and not r in seen:\n        seen[r] = len(digits)\n        r = r * 10\n        digits = digits + str(div(r, d))\n        r = r % d\n    if r == 0:\n        return [minus + whole, digits, \"\"]\n    start = seen[r]\n    return [minus + whole, digits[0:start], digits[start:len(digits)]]\n\ndef decimal_text(parts):\n    s = parts[0]\n    if parts[1] != \"\" or parts[2] != \"\":\n        s = s + \".\" + parts[1]\n        if parts[2] != \"\":\n            s = s + \"(\" + parts[2] + \")\"\n    return s\n\ndef from_decimal(s):\n    sg = 1\n    if len(s) > 0 and s[0] == \"-\":\n        sg = -1\n        s = s[1:len(s)]\n    whole = \"\"\n    fixed = \"\"\n    rep = \"\"\n    state = 0\n    for ch in s:\n        if ch >= \"0\" and ch <= \"9\":\n            if state == 0:\n                whole = whole + ch\n            elif state == 1:\n                fixed = fixed + ch\n            elif state == 2:\n                rep = rep + ch\n            else:\n                return []\n        elif ch == \".\" and state == 0:\n            state = 1\n        elif ch == \"(\" and state == 1:\n            state = 2\n        elif ch == \")\" and state == 2 and rep != \"\":\n            state = 3\n        else:\n            return []\n    if whole == \"\" or state == 2 or len(whole) + len(fixed) + len(rep) > 1000:\n        return []\n    tk = 1\n    for c in fixed:\n        tk = tk * 10\n    wf = int(whole + fixed)\n    if rep == \"\":\n        return reduced(sg * wf, tk)[0]\n    tm = 1\n    for c in rep:\n        tm = tm * 10\n    wfr = int(whole + fixed + rep)\n    return reduced(sg * (wfr - wf), tk * (tm - 1))[0]\n\ndef continued(n, d):\n    terms = []\n    while d != 0:\n        a = floor_div(n, d)\n        terms = terms + [a]\n        r = n - a * d\n        n = d\n        d = r\n    return terms\n\ndef convergents(terms):\n    out = []\n    p1 = 1\n    p2 = 0\n    q1 = 0\n    q2 = 1\n    for a in terms:\n        p = a * p1 + p2\n        q = a * q1 + q2\n        out = out + [[p, q]]\n        p2 = p1\n        p1 = p\n        q2 = q1\n        q1 = q\n    return out\n\ndef egyptian(n, d):\n    out = []\n    while n > 0:\n        if len(out) == 12:\n            return [out, False]\n        k = div(d + n - 1, n)\n        out = out + [k]\n        n = n * k - d\n        d = d * k\n        f = reduced(n, d)[0]\n        n = f[0]\n        d = f[1]\n    return [out, True]\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n1\nabc\n1/0\n1/2/3\n1234567890123/1\n\n1\n0/5\n2\n-22/7\n3\nabc\n.5\n1.(\n\n4\n-7/3\n5\n3/2\n5\n-1/2\n6\n",
      "screen": "== Fraction tutor ==\nFractions in lowest terms, their decimals, continued fractions and Egyptian fractions.\n\n1) lowest terms  2) to a decimal  3) from a decimal  4) continued fraction  5) Egyptian fraction  6) quit\nchoice> 0\nPick a number from 1 to 6.\n\n1) lowest terms  2) to a decimal  3) from a decimal  4) continued fraction  5) Egyptian fraction  6) quit\nchoice> x\nPick a number from 1 to 6.\n\n1) lowest terms  2) to a decimal  3) from a decimal  4) continued fraction  5) Egyptian fraction  6) quit\nchoice> 1\nfraction> abc\nType a fraction like 1071/462 or -3/4, or a whole number - up to 12 digits each, and the bottom not 0.\nfraction> 1/0\nType a fraction like 1071/462 or -3/4, or a whole number - up to 12 digits each, and the bottom not 0.\nfraction> 1/2/3\nType a fraction like 1071/462 or -3/4, or a whole number - up to 12 digits each, and the bottom not 0.\nfraction> 1234567890123/1\nType a fraction like 1071/462 or -3/4, or a whole number - up to 12 digits each, and the bottom not 0.\nfraction> \nCancelled.\n\n1) lowest terms  2) to a decimal  3) from a decimal  4) continued fraction  5) Egyptian fraction  6) quit\nchoice> 1\nfraction> 0/5\n  0 = 0 x 5 + 0\n0/5 is 0.\n\n1) lowest terms  2) to a decimal  3) from a decimal  4) continued fraction  5) Egyptian fraction  6) quit\nchoice> 2\nfraction> -22/7\n-22/7 = -3.(142857)\nThe digits 142857 repeat for ever, 6 at a time.\n\n1) lowest terms  2) to a decimal  3) from a decimal  4) continued fraction  5) Egyptian fraction  6) quit\nchoice> 3\ndecimal (repeating digits in brackets, like 0.1(6))> abc\nType digits, with a point if there is a fraction part and any repeating digits in brackets after it - like 3, 2.5, 0.(3) or 1.2(45).\ndecimal (repeating digits in brackets, like 0.1(6))> .5\nType digits, with a point if there is a fraction part and any repeating digits in brackets after it - like 3, 2.5, 0.(3) or 1.2(45).\ndecimal (repeating digits in brackets, like 0.1(6))> 1.(\nType digits, with a point if there is a fraction part and any repeating digits in brackets after it - like 3, 2.5, 0.(3) or 1.2(45).\ndecimal (repeating digits in brackets, like 0.1(6))> \nCancelled.\n\n1) lowest terms  2) to a decimal  3) from a decimal  4) continued fraction  5) Egyptian fraction  6) quit\nchoice> 4\nfraction> -7/3\n-7/3 = [-3; 1, 2]\nConvergents - each closer than any fraction with a smaller denominator:\n  -3, -2, -7/3\n\n1) lowest terms  2) to a decimal  3) from a decimal  4) continued fraction  5) Egyptian fraction  6) quit\nchoice> 5\nfraction between 0 and 1> 3/2\nEgyptian fractions here are for a fraction strictly between 0 and 1.\n\n1) lowest terms  2) to a decimal  3) from a decimal  4) continued fraction  5) Egyptian fraction  6) quit\nchoice> 5\nfraction between 0 and 1> -1/2\nEgyptian fractions here are for a fraction strictly between 0 and 1.\n\n1) lowest terms  2) to a decimal  3) from a decimal  4) continued fraction  5) Egyptian fraction  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n1071/462\n2\n1/7\n2\n7/12\n2\n3/8\n3\n0.1(6)\n3\n1.2(45)\n4\n415/93\n4\n103993/33102\n5\n4/13\n5\n5/121\n6\n",
      "screen": "== Fraction tutor ==\nFractions in lowest terms, their decimals, continued fractions and Egyptian fractions.\n\n1) lowest terms  2) to a decimal  3) from a decimal  4) continued fraction  5) Egyptian fraction  6) quit\nchoice> 1\nfraction> 1071/462\n  1071 = 2 x 462 + 147\n  462 = 3 x 147 + 21\n  147 = 7 x 21 + 0\nThe greatest common divisor is 21, so 1071/462 = 51/22.\n\n1) lowest terms  2) to a decimal  3) from a decimal  4) continued fraction  5) Egyptian fraction  6) quit\nchoice> 2\nfraction> 1/7\n1/7 = 0.(142857)\nThe digits 142857 repeat for ever, 6 at a time.\n\n1) lowest terms  2) to a decimal  3) from a decimal  4) continued fraction  5) Egyptian fraction  6) quit\nchoice> 2\nfraction> 7/12\n7/12 = 0.58(3)\nThe digit 3 repeats for ever.\n\n1) lowest terms  2) to a decimal  3) from a decimal  4) continued fraction  5) Egyptian fraction  6) quit\nchoice> 2\nfraction> 3/8\n3/8 = 0.375\nThe decimal ends: the denominator has no prime factor but 2 and 5.\n\n1) lowest terms  2) to a decimal  3) from a decimal  4) continued fraction  5) Egyptian fraction  6) quit\nchoice> 3\ndecimal (repeating digits in brackets, like 0.1(6))> 0.1(6)\n0.1(6) = 1/6\n\n1) lowest terms  2) to a decimal  3) from a decimal  4) continued fraction  5) Egyptian fraction  6) quit\nchoice> 3\ndecimal (repeating digits in brackets, like 0.1(6))> 1.2(45)\n1.2(45) = 137/110\n\n1) lowest terms  2) to a decimal  3) from a decimal  4) continued fraction  5) Egyptian fraction  6) quit\nchoice> 4\nfraction> 415/93\n415/93 = [4; 2, 6, 7]\nConvergents - each closer than any fraction with a smaller denominator:\n  4, 9/2, 58/13, 415/93\n\n1) lowest terms  2) to a decimal  3) from a decimal  4) continued fraction  5) Egyptian fraction  6) quit\nchoice> 4\nfraction> 103993/33102\n103993/33102 = [3; 7, 15, 1, 292]\nConvergents - each closer than any fraction with a smaller denominator:\n  3, 22/7, 333/106, 355/113, 103993/33102\n\n1) lowest terms  2) to a decimal  3) from a decimal  4) continued fraction  5) Egyptian fraction  6) quit\nchoice> 5\nfraction between 0 and 1> 4/13\n4/13 = 1/4 + 1/18 + 1/468\n\n1) lowest terms  2) to a decimal  3) from a decimal  4) continued fraction  5) Egyptian fraction  6) quit\nchoice> 5\nfraction between 0 and 1> 5/121\n5/121 = 1/25 + 1/757 + 1/763309 + 1/873960180913 + 1/1527612795642093418846225\n\n1) lowest terms  2) to a decimal  3) from a decimal  4) continued fraction  5) Egyptian fraction  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "euclidean-gcd-recursive",
      "caseId": "070-euclidean-gcd-recursive",
      "title": "Euclidean GCD (recursive)"
    },
    {
      "slug": "fraction-arithmetic",
      "caseId": "112-fraction-arithmetic",
      "title": "Fraction arithmetic (exact rationals)"
    }
  ],
  "updated": "2026-10-14"
}
