{
  "id": "P082",
  "slug": "root-finder",
  "key": "P082-root-finder",
  "title": "Root finder",
  "summary": "Bisection and Newton's method side by side on square roots and on roots of polynomials, in exact fractions, with the correct decimals of every step counted exactly - one bit a step against digits that double.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P082 - Root finder\n\nBisection and Newton's method side by side, on square roots and on the\nroots of polynomials. Every number is an exact fraction, so the correct\ndecimals of every step can be counted exactly - one bit a step against\ndigits that double.\n\n- `main.eml` - the menu, reading numbers and polynomials, and the race\n  table\n- `roots.eml` - fractions, Horner's rule and the derivative, bisection,\n  Newton's method, the reference root, and counting correct decimals\n\nHow each part works:\n\n- Bisection, as in the corpus case `bisection-root-finder`: an interval\n  where the function changes sign holds a root. Halve it and keep the half\n  whose ends still differ in sign. Each step gains one bit, about a third\n  of a decimal digit; it needs only the sign, and it cannot fail.\n- Newton's method, as in the corpus case `newton-sqrt`: replace x by\n  x - f(x)/f'(x), the point where the tangent crosses zero. Near a simple\n  root the number of correct digits about doubles each step. It needs the\n  derivative, and from a bad start it can settle on a different root.\n- The corpus cases work in floats, where both stop at about 16 digits.\n  Here bisection's midpoints are exact fractions. Newton's values are\n  rounded to 60 decimals each step, as a float keeps a fixed number of\n  bits; without that, its fractions would double in length every step.\n- Correct decimals: the root is first found by 220 halvings, which is\n  within (b - a) / 2^221 of it - below 10^-63 for any interval allowed.\n  A value then has d correct decimals when it is within 10^-d of that\n  root.\n\nWhat is checked: menu choices 1 to 3; a whole number from 2 to 1,000,000\nfor a square root (a perfect square is reported at once); 2 to 6 whole\ncoefficients from -1000 to 1000, the first not 0; an interval of two whole\nnumbers from -100 to 100, the smaller first, where the polynomial changes\nsign. An empty answer cancels.\n\nSessions: `sessions/basic.in` gives:\n- the square roots of 2 and 10: bisection needs 48 and 49 steps for 15\n  decimals, Newton 5 and 6;\n- the root of x^3 - x - 2 between 1 and 2: Newton in 4 steps;\n- x^3 - 6x^2 + 11x - 6 between 0 and 5: bisection finds 3, while Newton,\n  started at 2.5, lands on the root 1 in one step;\n- the negative root of x^2 - 2.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- 1, 1000001 and abc, then 16, a perfect square;\n- coefficients \"0 1\", \"1\", seven numbers and \"1 x\";\n- for x^2 - 4: intervals \"2 1\" and \"-200 0\", \"3 4\" with no sign change,\n  and \"2 5\", where 2 is a root exactly.\n\nBuilt on the verified corpus cases `bisection-root-finder` (halving an\ninterval with a sign change) and `newton-sqrt` (the tangent step, and its\ndigits that double).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P082 root finder: bisection and Newton's method side by side, on square\n# roots and on roots of polynomials, with every step's correct digits\n# counted exactly.\nimport roots\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 integer(s):\n    # A whole number, maybe negative, of up to 7 digits, as [ok, value].\n    s => t\n    1 => sg\n    if len(t) > 1 and t[0] == \"-\":\n        t[1:len(t)] => t\n        -1 => sg\n    if len(t) == 0 or len(t) > 7:\n        return [False, 0]\n    0 => n\n    for c in t:\n        if not (c >= \"0\" and c <= \"9\"):\n            return [False, 0]\n        n * 10 + int(c) => n\n    return [True, sg * n]\n\ndef numbers(s):\n    # \"1 0 -2\" -> [1, 0, -2], or [] if any is not a whole number.\n    [] => out\n    \"\" => w\n    for c in s + \" \":\n        if c == \" \":\n            if w != \"\":\n                integer(w) => v\n                if not v[0]:\n                    return []\n                out + [v[1]] => out\n            \"\" => w\n        else:\n            w + c => w\n    return out\n\ndef lpad(s, n):\n    while len(s) < n:\n        \" \" + s => s\n    return s\n\ndef pad(s, n):\n    while len(s) < n:\n        s + \" \" => s\n    return s\n\ndef poly_text(cs):\n    len(cs) - 1 => n\n    \"\" => s\n    for i in [0:n]:\n        cs[i] => c\n        n - i => p\n        if c != 0:\n            c => a\n            if a < 0:\n                0 - a => a\n            if s == \"\":\n                if c < 0:\n                    \"-\" => s\n            elif c < 0:\n                s + \" - \" => s\n            else:\n                s + \" + \" => s\n            if a != 1 or p == 0:\n                s + str(a) => s\n            if p == 1:\n                s + \"x\" => s\n            elif p > 1:\n                s + \"x^\" + str(p) => s\n    return s\n\ndef race(cs, a, b, x0, label):\n    # The two methods on the root of cs in [a, b], step by step.\n    roots.reference(cs, a, b) => ref\n    roots.bisect_steps(cs, a, b, 70) => bi\n    roots.newton_steps(cs, x0, 30, 60) => nw\n    (\"The root of \" + label + \" is \" + roots.decimal(roots.rounded(ref, 30), 30) + \"...\") ^0\n    [] => rows\n    10 => wb\n    10 => wn\n    for k in [0:7]:\n        roots.decimal(bi[0][k], 15) => bs\n        str(roots.correct(bi[0][k], ref, 60)) => bd\n        \"-\" => ns\n        \"\" => nd\n        if k < len(nw[0]):\n            roots.decimal(nw[0][k], 15) => ns\n            str(roots.correct(nw[0][k], ref, 60)) => nd\n        if len(bs) > wb:\n            len(bs) => wb\n        if len(ns) > wn:\n            len(ns) => wn\n        rows + [[bs, bd, ns, nd]] => rows\n    (\"  step  \" + pad(\"bisection\", wb) + \"  right  \" + pad(\"Newton\", wn) + \"  right\") ^0\n    for k in [0:7]:\n        rows[k] => r\n        (\"  \" + lpad(str(k + 1), 4) + \"  \" + pad(r[0], wb) + lpad(r[1], 7) + \"  \" + pad(r[2], wn) + lpad(r[3], 7)) ^0\n    -1 => need_b\n    for k in [0:69]:\n        if need_b == -1 and roots.correct(bi[0][k], ref, 60) >= 15:\n            k + 1 => need_b\n    -1 => need_n\n    for k in [0:len(nw[0]) - 1]:\n        if need_n == -1 and roots.correct(nw[0][k], ref, 60) >= 15:\n            k + 1 => need_n\n    if need_b > 0:\n        (\"Bisection has 15 decimals right after \" + str(need_b) + \" steps: it halves the interval, about a third of a digit a step.\") ^0\n    if need_n > 0:\n        (\"Newton has them after \" + str(need_n) + \" steps: near the root the number of right digits about doubles each step.\") ^0\n    elif nw[1] != \"\":\n        (\"Newton stopped after \" + str(len(nw[0])) + \" steps: \" + nw[1] + \".\") ^0\n    elif len(nw[0]) >= 2 and nw[0][len(nw[0]) - 1] == nw[0][len(nw[0]) - 2]:\n        (\"Newton settled on a different root, \" + roots.decimal(nw[0][len(nw[0]) - 1], 15) + \": from its start the slope sent it there, not to the root bisection keeps.\") ^0\n    else:\n        \"Newton did not get 15 decimals of this root in 30 steps: it wandered.\" ^0\n    \"(Bisection's midpoints are exact fractions; Newton's values are kept to 60 decimals, as a float keeps a fixed number of bits.)\" ^0\n\ndef square_root():\n    while True:\n        trim(input(\"number (2 to 1000000)> \")) => s\n        if s == \"\":\n            \"Cancelled.\" ^0\n            return\n        integer(s) => v\n        if v[0] and v[1] >= 2 and v[1] <= 1000000:\n            v[1] => n\n            1 => k\n            while (k + 1) * (k + 1) <= n:\n                k + 1 => k\n            if k * k == n:\n                (str(n) + \" is \" + str(k) + \" squared; there is nothing to find.\") ^0\n                return\n            race([1, 0, 0 - n], [k, 1], [k + 1, 1], [n, 1], \"x^2 - \" + str(n))\n            (\"(Bisection starts from \" + str(k) + \" and \" + str(k + 1) + \", Newton from \" + str(n) + \".)\") ^0\n            return\n        \"Type a whole number from 2 to 1000000.\" ^0\n\ndef poly_root():\n    while True:\n        trim(input(\"coefficients, highest power first (like 1 0 -2 for x^2 - 2)> \")) => s\n        if s == \"\":\n            \"Cancelled.\" ^0\n            return\n        numbers(s) => cs\n        True => ok\n        for c in cs:\n            if c < -1000 or c > 1000:\n                False => ok\n        if ok and len(cs) >= 2 and len(cs) <= 6 and cs[0] != 0:\n            poly_text(cs) => label\n            while True:\n                trim(input(\"an interval where it changes sign (like 1 2)> \")) => t\n                if t == \"\":\n                    \"Cancelled.\" ^0\n                    return\n                numbers(t) => ab\n                if len(ab) == 2 and ab[0] < ab[1] and ab[0] >= -100 and ab[1] <= 100:\n                    [ab[0], 1] => a\n                    [ab[1], 1] => b\n                    roots.sign(roots.value(cs, a)) => sa\n                    roots.sign(roots.value(cs, b)) => sb\n                    if sa == 0:\n                        (str(ab[0]) + \" is a root of \" + label + \" exactly.\") ^0\n                        return\n                    if sb == 0:\n                        (str(ab[1]) + \" is a root of \" + label + \" exactly.\") ^0\n                        return\n                    if sa == sb:\n                        (label + \" has the same sign at \" + str(ab[0]) + \" and \" + str(ab[1]) + \", so bisection cannot start there.\") ^0\n                    else:\n                        race(cs, a, b, roots.mul(roots.add(a, b), [1, 2]), label)\n                        \"(Both start from the interval; Newton from its middle.)\" ^0\n                        return\n                else:\n                    \"Type two whole numbers from -100 to 100, the smaller first.\" ^0\n        else:\n            \"Type 2 to 6 whole numbers from -1000 to 1000, the first not 0.\" ^0\n\n\"== Root finder ==\" ^0\n\"Bisection and Newton's method, step by step, with the right digits counted exactly.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) square root  2) root of a polynomial  3) quit\" ^0\n    trim(input(\"choice> \")) => choice\n    if choice == \"1\":\n        square_root()\n    elif choice == \"2\":\n        poly_root()\n    elif choice == \"3\":\n        False => running\n    else:\n        \"Pick a number from 1 to 3.\" ^0\n\"Bye.\" ^0\n",
      "python": "import roots\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 integer(s):\n    t = s\n    sg = 1\n    if len(t) > 1 and t[0] == \"-\":\n        t = t[1:len(t)]\n        sg = -1\n    if len(t) == 0 or len(t) > 7:\n        return [False, 0]\n    n = 0\n    for c in t:\n        if not (c >= \"0\" and c <= \"9\"):\n            return [False, 0]\n        n = n * 10 + int(c)\n    return [True, sg * n]\n\ndef numbers(s):\n    out = []\n    w = \"\"\n    for c in s + \" \":\n        if c == \" \":\n            if w != \"\":\n                v = integer(w)\n                if not v[0]:\n                    return []\n                out = out + [v[1]]\n            w = \"\"\n        else:\n            w = w + c\n    return out\n\ndef lpad(s, n):\n    while len(s) < n:\n        s = \" \" + s\n    return s\n\ndef pad(s, n):\n    while len(s) < n:\n        s = s + \" \"\n    return s\n\ndef poly_text(cs):\n    n = len(cs) - 1\n    s = \"\"\n    for i in range(0, n+1):\n        c = cs[i]\n        p = n - i\n        if c != 0:\n            a = c\n            if a < 0:\n                a = 0 - a\n            if s == \"\":\n                if c < 0:\n                    s = \"-\"\n            elif c < 0:\n                s = s + \" - \"\n            else:\n                s = s + \" + \"\n            if a != 1 or p == 0:\n                s = s + str(a)\n            if p == 1:\n                s = s + \"x\"\n            elif p > 1:\n                s = s + \"x^\" + str(p)\n    return s\n\ndef race(cs, a, b, x0, label):\n    ref = roots.reference(cs, a, b)\n    bi = roots.bisect_steps(cs, a, b, 70)\n    nw = roots.newton_steps(cs, x0, 30, 60)\n    print(\"The root of \" + label + \" is \" + roots.decimal(roots.rounded(ref, 30), 30) + \"...\")\n    rows = []\n    wb = 10\n    wn = 10\n    for k in range(0, 8):\n        bs = roots.decimal(bi[0][k], 15)\n        bd = str(roots.correct(bi[0][k], ref, 60))\n        ns = \"-\"\n        nd = \"\"\n        if k < len(nw[0]):\n            ns = roots.decimal(nw[0][k], 15)\n            nd = str(roots.correct(nw[0][k], ref, 60))\n        if len(bs) > wb:\n            wb = len(bs)\n        if len(ns) > wn:\n            wn = len(ns)\n        rows = rows + [[bs, bd, ns, nd]]\n    print(\"  step  \" + pad(\"bisection\", wb) + \"  right  \" + pad(\"Newton\", wn) + \"  right\")\n    for k in range(0, 8):\n        r = rows[k]\n        print(\"  \" + lpad(str(k + 1), 4) + \"  \" + pad(r[0], wb) + lpad(r[1], 7) + \"  \" + pad(r[2], wn) + lpad(r[3], 7))\n    need_b = -1\n    for k in range(0, 70):\n        if need_b == -1 and roots.correct(bi[0][k], ref, 60) >= 15:\n            need_b = k + 1\n    need_n = -1\n    for k in range(0, len(nw[0])):\n        if need_n == -1 and roots.correct(nw[0][k], ref, 60) >= 15:\n            need_n = k + 1\n    if need_b > 0:\n        print(\"Bisection has 15 decimals right after \" + str(need_b) + \" steps: it halves the interval, about a third of a digit a step.\")\n    if need_n > 0:\n        print(\"Newton has them after \" + str(need_n) + \" steps: near the root the number of right digits about doubles each step.\")\n    elif nw[1] != \"\":\n        print(\"Newton stopped after \" + str(len(nw[0])) + \" steps: \" + nw[1] + \".\")\n    elif len(nw[0]) >= 2 and nw[0][len(nw[0]) - 1] == nw[0][len(nw[0]) - 2]:\n        print(\"Newton settled on a different root, \" + roots.decimal(nw[0][len(nw[0]) - 1], 15) + \": from its start the slope sent it there, not to the root bisection keeps.\")\n    else:\n        print(\"Newton did not get 15 decimals of this root in 30 steps: it wandered.\")\n    print(\"(Bisection's midpoints are exact fractions; Newton's values are kept to 60 decimals, as a float keeps a fixed number of bits.)\")\n\ndef square_root():\n    while True:\n        s = trim(input(\"number (2 to 1000000)> \"))\n        if s == \"\":\n            print(\"Cancelled.\")\n            return\n        v = integer(s)\n        if v[0] and v[1] >= 2 and v[1] <= 1000000:\n            n = v[1]\n            k = 1\n            while (k + 1) * (k + 1) <= n:\n                k = k + 1\n            if k * k == n:\n                print(str(n) + \" is \" + str(k) + \" squared; there is nothing to find.\")\n                return\n            race([1, 0, 0 - n], [k, 1], [k + 1, 1], [n, 1], \"x^2 - \" + str(n))\n            print(\"(Bisection starts from \" + str(k) + \" and \" + str(k + 1) + \", Newton from \" + str(n) + \".)\")\n            return\n        print(\"Type a whole number from 2 to 1000000.\")\n\ndef poly_root():\n    while True:\n        s = trim(input(\"coefficients, highest power first (like 1 0 -2 for x^2 - 2)> \"))\n        if s == \"\":\n            print(\"Cancelled.\")\n            return\n        cs = numbers(s)\n        ok = True\n        for c in cs:\n            if c < -1000 or c > 1000:\n                ok = False\n        if ok and len(cs) >= 2 and len(cs) <= 6 and cs[0] != 0:\n            label = poly_text(cs)\n            while True:\n                t = trim(input(\"an interval where it changes sign (like 1 2)> \"))\n                if t == \"\":\n                    print(\"Cancelled.\")\n                    return\n                ab = numbers(t)\n                if len(ab) == 2 and ab[0] < ab[1] and ab[0] >= -100 and ab[1] <= 100:\n                    a = [ab[0], 1]\n                    b = [ab[1], 1]\n                    sa = roots.sign(roots.value(cs, a))\n                    sb = roots.sign(roots.value(cs, b))\n                    if sa == 0:\n                        print(str(ab[0]) + \" is a root of \" + label + \" exactly.\")\n                        return\n                    if sb == 0:\n                        print(str(ab[1]) + \" is a root of \" + label + \" exactly.\")\n                        return\n                    if sa == sb:\n                        print(label + \" has the same sign at \" + str(ab[0]) + \" and \" + str(ab[1]) + \", so bisection cannot start there.\")\n                    else:\n                        race(cs, a, b, roots.mul(roots.add(a, b), [1, 2]), label)\n                        print(\"(Both start from the interval; Newton from its middle.)\")\n                        return\n                else:\n                    print(\"Type two whole numbers from -100 to 100, the smaller first.\")\n        else:\n            print(\"Type 2 to 6 whole numbers from -1000 to 1000, the first not 0.\")\n\nprint(\"== Root finder ==\")\nprint(\"Bisection and Newton's method, step by step, with the right digits counted exactly.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) square root  2) root of a polynomial  3) quit\")\n    choice = trim(input(\"choice> \"))\n    if choice == \"1\":\n        square_root()\n    elif choice == \"2\":\n        poly_root()\n    elif choice == \"3\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 3.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "roots.eml",
      "eml": "# P082 root finder - bisection and Newton's method on square roots and on\n# polynomials with whole-number coefficients, in exact fractions [n, d]\n# (d > 0), so how many digits each step has right can be counted exactly.\n# The corpus cases bisection-root-finder and newton-sqrt do the same in\n# floats, where both stop at about 16 digits.\n\ndef gcd(a, b):\n    if a < 0:\n        0 - a => a\n    while b != 0:\n        a % b => r\n        b => a\n        r => b\n    return a\n\ndef frac(n, d):\n    if d < 0:\n        0 - n => n\n        0 - d => d\n    gcd(n, d) => g\n    if g > 1:\n        divide(n, g) => n\n        divide(d, g) => d\n    return [n, d]\n\ndef divide(a, b):\n    # a / b for b > 0 that divides a exactly (a may be negative), by long\n    # division digit by digit - int(a / b) would go through a float.\n    if a < 0:\n        return 0 - divide(0 - a, b)\n    0 => q\n    0 => r\n    for ch in str(a):\n        r * 10 + int(ch) => r\n        0 => k\n        while r >= b:\n            r - b => r\n            k + 1 => k\n        q * 10 + k => q\n    return q\n\ndef add(x, y):\n    return frac(x[0] * y[1] + y[0] * x[1], x[1] * y[1])\n\ndef sub(x, y):\n    return frac(x[0] * y[1] - y[0] * x[1], x[1] * y[1])\n\ndef mul(x, y):\n    return frac(x[0] * y[0], x[1] * y[1])\n\ndef div(x, y):\n    return frac(x[0] * y[1], x[1] * y[0])\n\ndef value(coeffs, x):\n    # Horner's rule; coeffs are whole numbers, highest power first.\n    [0, 1] => v\n    for c in coeffs:\n        add(mul(v, x), [c, 1]) => v\n    return v\n\ndef slope(coeffs):\n    # The derivative's coefficients.\n    len(coeffs) - 1 => n\n    [] => out\n    for i in [0:n - 1]:\n        out + [coeffs[i] * (n - i)] => out\n    return out\n\ndef sign(x):\n    if x[0] > 0:\n        return 1\n    if x[0] < 0:\n        return -1\n    return 0\n\ndef rounded(x, places):\n    # x to the nearest multiple of 10^-places (halves up): Newton's values\n    # are kept to this many places, as a float keeps a fixed number of bits.\n    1 => s\n    for i in [1:places]:\n        s * 10 => s\n    x[0] * s * 2 + x[1] => n\n    2 * x[1] => d\n    # floor(n / d), with n possibly negative\n    if n >= 0:\n        divide(n - n % d, d) => q\n    else:\n        0 - divide((0 - n) + (d - (0 - n) % d) % d, d) => q\n    return frac(q, s)\n\ndef bisect_steps(coeffs, a, b, steps):\n    # The midpoints of `steps` halvings of [a, b], where coeffs changes\n    # sign: each time keep the half whose ends still differ in sign.\n    # Returns [midpoints, a, b].\n    sign(value(coeffs, a)) => sa\n    [] => mids\n    for k in [1:steps]:\n        mul(add(a, b), [1, 2]) => m\n        mids + [m] => mids\n        sign(value(coeffs, m)) => sm\n        if sm == 0:\n            m => a\n            m => b\n        elif sm == sa:\n            m => a\n        else:\n            m => b\n    return [mids, a, b]\n\ndef newton_steps(coeffs, x, steps, places):\n    # x <- x - f(x)/f'(x), each value kept to `places` decimals. Returns\n    # [values, \"\" or why it stopped].\n    slope(coeffs) => d\n    [] => xs\n    for k in [1:steps]:\n        value(d, x) => fd\n        if fd[0] == 0:\n            return [xs, \"the slope is 0 there\"]\n        rounded(sub(x, div(value(coeffs, x), fd)), places) => x\n        xs + [x] => xs\n    return [xs, \"\"]\n\ndef reference(coeffs, a, b):\n    # The root in [a, b] by 220 halvings: within (b - a) / 2^221 of it, which\n    # for an interval up to 200 wide is below 10^-63 - past the 60 decimals\n    # the program ever counts.\n    bisect_steps(coeffs, a, b, 220) => r\n    return mul(add(r[1], r[2]), [1, 2])\n\ndef correct(x, ref, most):\n    # How many decimals x has right: the largest d <= most with\n    # |x - ref| < 10^-d.\n    sub(x, ref) => e\n    e[0] => n\n    if n < 0:\n        0 - n => n\n    0 => d\n    1 => p\n    while d < most and n * p * 10 < e[1]:\n        d + 1 => d\n        p * 10 => p\n    return d\n\ndef decimal(x, places):\n    # x to `places` decimals, cut off (not rounded), with its sign.\n    x[0] => n\n    \"\" => sg\n    if n < 0:\n        \"-\" => sg\n        0 - n => n\n    divide(n - n % x[1], x[1]) => w\n    n % x[1] => r\n    \"\" => digits\n    for i in [1:places]:\n        r * 10 => r\n        0 => k\n        while r >= x[1]:\n            r - x[1] => r\n            k + 1 => k\n        digits + str(k) => digits\n    return sg + str(w) + \".\" + digits\n",
      "python": "def gcd(a, b):\n    if a < 0:\n        a = 0 - a\n    while b != 0:\n        r = a % b\n        a = b\n        b = r\n    return a\n\ndef frac(n, d):\n    if d < 0:\n        n = 0 - n\n        d = 0 - d\n    g = gcd(n, d)\n    if g > 1:\n        n = divide(n, g)\n        d = divide(d, g)\n    return [n, d]\n\ndef divide(a, b):\n    if a < 0:\n        return 0 - divide(0 - a, b)\n    q = 0\n    r = 0\n    for ch in str(a):\n        r = r * 10 + int(ch)\n        k = 0\n        while r >= b:\n            r = r - b\n            k = k + 1\n        q = q * 10 + k\n    return q\n\ndef add(x, y):\n    return frac(x[0] * y[1] + y[0] * x[1], x[1] * y[1])\n\ndef sub(x, y):\n    return frac(x[0] * y[1] - y[0] * x[1], x[1] * y[1])\n\ndef mul(x, y):\n    return frac(x[0] * y[0], x[1] * y[1])\n\ndef div(x, y):\n    return frac(x[0] * y[1], x[1] * y[0])\n\ndef value(coeffs, x):\n    v = [0, 1]\n    for c in coeffs:\n        v = add(mul(v, x), [c, 1])\n    return v\n\ndef slope(coeffs):\n    n = len(coeffs) - 1\n    out = []\n    for i in range(0, n):\n        out = out + [coeffs[i] * (n - i)]\n    return out\n\ndef sign(x):\n    if x[0] > 0:\n        return 1\n    if x[0] < 0:\n        return -1\n    return 0\n\ndef rounded(x, places):\n    s = 1\n    for i in range(1, places+1):\n        s = s * 10\n    n = x[0] * s * 2 + x[1]\n    d = 2 * x[1]\n    if n >= 0:\n        q = divide(n - n % d, d)\n    else:\n        q = 0 - divide(0 - n + (d - (0 - n) % d) % d, d)\n    return frac(q, s)\n\ndef bisect_steps(coeffs, a, b, steps):\n    sa = sign(value(coeffs, a))\n    mids = []\n    for k in range(1, steps+1):\n        m = mul(add(a, b), [1, 2])\n        mids = mids + [m]\n        sm = sign(value(coeffs, m))\n        if sm == 0:\n            a = m\n            b = m\n        elif sm == sa:\n            a = m\n        else:\n            b = m\n    return [mids, a, b]\n\ndef newton_steps(coeffs, x, steps, places):\n    d = slope(coeffs)\n    xs = []\n    for k in range(1, steps+1):\n        fd = value(d, x)\n        if fd[0] == 0:\n            return [xs, \"the slope is 0 there\"]\n        x = rounded(sub(x, div(value(coeffs, x), fd)), places)\n        xs = xs + [x]\n    return [xs, \"\"]\n\ndef reference(coeffs, a, b):\n    r = bisect_steps(coeffs, a, b, 220)\n    return mul(add(r[1], r[2]), [1, 2])\n\ndef correct(x, ref, most):\n    e = sub(x, ref)\n    n = e[0]\n    if n < 0:\n        n = 0 - n\n    d = 0\n    p = 1\n    while d < most and n * p * 10 < e[1]:\n        d = d + 1\n        p = p * 10\n    return d\n\ndef decimal(x, places):\n    n = x[0]\n    sg = \"\"\n    if n < 0:\n        sg = \"-\"\n        n = 0 - n\n    w = divide(n - n % x[1], x[1])\n    r = n % x[1]\n    digits = \"\"\n    for i in range(1, places+1):\n        r = r * 10\n        k = 0\n        while r >= x[1]:\n            r = r - x[1]\n            k = k + 1\n        digits = digits + str(k)\n    return sg + str(w) + \".\" + digits\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n1\n1\n1000001\nabc\n16\n2\n0 1\n1\n1 2 3 4 5 6 7\n1 x\n1 0 -4\n2 1\n-200 0\n3 4\n2 5\n3\n",
      "screen": "== Root finder ==\nBisection and Newton's method, step by step, with the right digits counted exactly.\n\n1) square root  2) root of a polynomial  3) quit\nchoice> 0\nPick a number from 1 to 3.\n\n1) square root  2) root of a polynomial  3) quit\nchoice> x\nPick a number from 1 to 3.\n\n1) square root  2) root of a polynomial  3) quit\nchoice> 1\nnumber (2 to 1000000)> 1\nType a whole number from 2 to 1000000.\nnumber (2 to 1000000)> 1000001\nType a whole number from 2 to 1000000.\nnumber (2 to 1000000)> abc\nType a whole number from 2 to 1000000.\nnumber (2 to 1000000)> 16\n16 is 4 squared; there is nothing to find.\n\n1) square root  2) root of a polynomial  3) quit\nchoice> 2\ncoefficients, highest power first (like 1 0 -2 for x^2 - 2)> 0 1\nType 2 to 6 whole numbers from -1000 to 1000, the first not 0.\ncoefficients, highest power first (like 1 0 -2 for x^2 - 2)> 1\nType 2 to 6 whole numbers from -1000 to 1000, the first not 0.\ncoefficients, highest power first (like 1 0 -2 for x^2 - 2)> 1 2 3 4 5 6 7\nType 2 to 6 whole numbers from -1000 to 1000, the first not 0.\ncoefficients, highest power first (like 1 0 -2 for x^2 - 2)> 1 x\nType 2 to 6 whole numbers from -1000 to 1000, the first not 0.\ncoefficients, highest power first (like 1 0 -2 for x^2 - 2)> 1 0 -4\nan interval where it changes sign (like 1 2)> 2 1\nType two whole numbers from -100 to 100, the smaller first.\nan interval where it changes sign (like 1 2)> -200 0\nType two whole numbers from -100 to 100, the smaller first.\nan interval where it changes sign (like 1 2)> 3 4\nx^2 - 4 has the same sign at 3 and 4, so bisection cannot start there.\nan interval where it changes sign (like 1 2)> 2 5\n2 is a root of x^2 - 4 exactly.\n\n1) square root  2) root of a polynomial  3) quit\nchoice> 3\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n2\n1\n10\n2\n1 0 -1 -2\n1 2\n2\n1 -6 11 -6\n0 5\n2\n1 0 -2\n-2 -1\n3\n",
      "screen": "== Root finder ==\nBisection and Newton's method, step by step, with the right digits counted exactly.\n\n1) square root  2) root of a polynomial  3) quit\nchoice> 1\nnumber (2 to 1000000)> 2\nThe root of x^2 - 2 is 1.414213562373095048801688724210...\n  step  bisection          right  Newton             right\n     1  1.500000000000000      1  1.500000000000000      1\n     2  1.250000000000000      0  1.416666666666666      2\n     3  1.375000000000000      1  1.414215686274509      5\n     4  1.437500000000000      1  1.414213562374689     11\n     5  1.406250000000000      2  1.414213562373095     24\n     6  1.421875000000000      2  1.414213562373095     48\n     7  1.414062500000000      3  1.414213562373095     60\n     8  1.417968750000000      2  1.414213562373095     60\nBisection has 15 decimals right after 48 steps: it halves the interval, about a third of a digit a step.\nNewton has them after 5 steps: near the root the number of right digits about doubles each step.\n(Bisection's midpoints are exact fractions; Newton's values are kept to 60 decimals, as a float keeps a fixed number of bits.)\n(Bisection starts from 1 and 2, Newton from 2.)\n\n1) square root  2) root of a polynomial  3) quit\nchoice> 1\nnumber (2 to 1000000)> 10\nThe root of x^2 - 10 is 3.162277660168379331998893544433...\n  step  bisection          right  Newton             right\n     1  3.500000000000000      0  5.500000000000000      0\n     2  3.250000000000000      1  3.659090909090909      0\n     3  3.125000000000000      1  3.196005081874647      1\n     4  3.187500000000000      1  3.162455622803890      3\n     5  3.156250000000000      2  3.162277665175674      8\n     6  3.171875000000000      2  3.162277660168379     17\n     7  3.164062500000000      2  3.162277660168379     35\n     8  3.160156250000000      2  3.162277660168379     60\nBisection has 15 decimals right after 49 steps: it halves the interval, about a third of a digit a step.\nNewton has them after 6 steps: near the root the number of right digits about doubles each step.\n(Bisection's midpoints are exact fractions; Newton's values are kept to 60 decimals, as a float keeps a fixed number of bits.)\n(Bisection starts from 3 and 4, Newton from 10.)\n\n1) square root  2) root of a polynomial  3) quit\nchoice> 2\ncoefficients, highest power first (like 1 0 -2 for x^2 - 2)> 1 0 -1 -2\nan interval where it changes sign (like 1 2)> 1 2\nThe root of x^3 - x - 2 is 1.521379706804567569604080832254...\n  step  bisection          right  Newton             right\n     1  1.500000000000000      1  1.521739130434782      3\n     2  1.750000000000000      0  1.521379805964786      7\n     3  1.625000000000000      0  1.521379706804575     14\n     4  1.562500000000000      1  1.521379706804567     28\n     5  1.531250000000000      2  1.521379706804567     56\n     6  1.515625000000000      2  1.521379706804567     60\n     7  1.523437500000000      2  1.521379706804567     60\n     8  1.519531250000000      2  1.521379706804567     60\nBisection has 15 decimals right after 49 steps: it halves the interval, about a third of a digit a step.\nNewton has them after 4 steps: near the root the number of right digits about doubles each step.\n(Bisection's midpoints are exact fractions; Newton's values are kept to 60 decimals, as a float keeps a fixed number of bits.)\n(Both start from the interval; Newton from its middle.)\n\n1) square root  2) root of a polynomial  3) quit\nchoice> 2\ncoefficients, highest power first (like 1 0 -2 for x^2 - 2)> 1 -6 11 -6\nan interval where it changes sign (like 1 2)> 0 5\nThe root of x^3 - 6x^2 + 11x - 6 is 3.000000000000000000000000000000...\n  step  bisection          right  Newton             right\n     1  2.500000000000000      0  1.000000000000000      0\n     2  3.750000000000000      0  1.000000000000000      0\n     3  3.125000000000000      0  1.000000000000000      0\n     4  2.812500000000000      0  1.000000000000000      0\n     5  2.968750000000000      1  1.000000000000000      0\n     6  3.046875000000000      1  1.000000000000000      0\n     7  3.007812500000000      2  1.000000000000000      0\n     8  2.988281250000000      1  1.000000000000000      0\nBisection has 15 decimals right after 51 steps: it halves the interval, about a third of a digit a step.\nNewton settled on a different root, 1.000000000000000: from its start the slope sent it there, not to the root bisection keeps.\n(Bisection's midpoints are exact fractions; Newton's values are kept to 60 decimals, as a float keeps a fixed number of bits.)\n(Both start from the interval; Newton from its middle.)\n\n1) square root  2) root of a polynomial  3) quit\nchoice> 2\ncoefficients, highest power first (like 1 0 -2 for x^2 - 2)> 1 0 -2\nan interval where it changes sign (like 1 2)> -2 -1\nThe root of x^2 - 2 is -1.414213562373095048801688724210...\n  step  bisection           right  Newton              right\n     1  -1.500000000000000      1  -1.416666666666666      2\n     2  -1.250000000000000      0  -1.414215686274509      5\n     3  -1.375000000000000      1  -1.414213562374689     11\n     4  -1.437500000000000      1  -1.414213562373095     24\n     5  -1.406250000000000      2  -1.414213562373095     48\n     6  -1.421875000000000      2  -1.414213562373095     60\n     7  -1.414062500000000      3  -1.414213562373095     60\n     8  -1.417968750000000      2  -1.414213562373095     60\nBisection has 15 decimals right after 48 steps: it halves the interval, about a third of a digit a step.\nNewton has them after 4 steps: near the root the number of right digits about doubles each step.\n(Bisection's midpoints are exact fractions; Newton's values are kept to 60 decimals, as a float keeps a fixed number of bits.)\n(Both start from the interval; Newton from its middle.)\n\n1) square root  2) root of a polynomial  3) quit\nchoice> 3\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "bisection-root-finder",
      "caseId": "110-bisection-root-finder",
      "title": "Bisection root finder"
    },
    {
      "slug": "newton-sqrt",
      "caseId": "120-newton-sqrt",
      "title": "Newton's method for square roots"
    }
  ],
  "updated": "2026-10-15"
}
