{
  "id": "P151",
  "slug": "tax-brackets",
  "key": "P151-tax-brackets",
  "title": "Tax brackets",
  "summary": "Income bands written from a sentence and read four ways - closed, half-open, open, left-open - the incomes on a band's end, the slab tax the reading decides and the marginal tax it cannot change.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P151 - Tax brackets\n\nIncome bands written from a sentence - 0-10000, 10000-40000, 40000-100000,\n100000 and above - read four ways, and the incomes that sit on a band's\nend.\n\n- `main.eml` - the menu, the bands under each reading, one income under\n  each, the partition check over the incomes on file, and adding one\n- `bands.eml` - the bands, the readings, the incomes on file, whether a\n  band takes an income, the bands that take it, the slab tax and the\n  marginal tax\n\nHow each part works:\n\n- As in the corpus case `boundary-inclusive-or-not`, \"between\" is four\n  questions - closed, half-open, open, left-open - and they part only at\n  the ends: 0, 10000, 40000 and 100000. Every income in the middle lands in\n  the same band under all four, which is why the difference survives every\n  test there.\n- Consecutive bands read closed take the three inner ends twice; read open\n  they take the four ends not at all; left-open misses only 0. Half-open\n  alone puts every income in exactly one band: the bands are a partition.\n- The slab tax charges the whole income at its band's rate, so the reading\n  decides it at an end: 10000 pays 0.00, 1000.00 or 3000.00 - read open no\n  band takes it, and the else branch charges the top rate.\n- The marginal tax charges each slice at its own band's rate. It is the\n  same under every reading, a slice's width not depending on which band\n  owns its end, and it does not jump.\n\nWhat is checked: menu choices 1 to 5; an income from 0 to 9999999. An\nempty answer cancels.\n\nSessions: `sessions/basic.in` gives:\n- the bands; incomes 10000, 25000, 100000 and 0;\n- the incomes on file; 40000 added, and the incomes on file again.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- incomes: a cancel, 12345678, 1e5 and -5;\n- adding: a cancel and x, then 99999; the incomes on file.\n\nBuilt on the verified corpus case `boundary-inclusive-or-not` (four\nreadings of a range, consecutive buckets that double-count or drop a\nboundary, and a partition check by counting).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P151 tax brackets: the bands and their four readings, one income under\n# each, the partition check over the incomes on file, and adding one.\nimport bands\n\ndef plain(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 pad(s, n):\n    s => out\n    while len(out) < n:\n        out + \" \" => out\n    return out\n\ndef lpad(s, n):\n    s => out\n    while len(out) < n:\n        \" \" + out => out\n    return out\n\ndef money(c):\n    c % 100 => p\n    if p < 10:\n        return str(int((c - p) / 100)) + \".0\" + str(p)\n    return str(int((c - p) / 100)) + \".\" + str(p)\n\ndef interval(reading, b):\n    if reading == \"closed\" or reading == \"half-open\":\n        \"[\" => left\n    else:\n        \"(\" => left\n    if b[1] < 0:\n        return left + str(b[0]) + \", and up)\"\n    if reading == \"closed\" or reading == \"left-open\":\n        \"]\" => right\n    else:\n        \")\" => right\n    return left + str(b[0]) + \", \" + str(b[1]) + right\n\ndef show_bands():\n    \"The sentence: 0-10000 at 0%, 10000-40000 at 10%, 40000-100000 at 20%, 100000 and above at 30%.\" ^0\n    (\"  \" + pad(\"band\", 6) + pad(\"closed\", 18) + pad(\"half-open\", 18) + pad(\"open\", 18) + \"left-open\") ^0\n    1 => k\n    for b in bands.bands():\n        (\"  \" + pad(str(k) + \" \" + str(b[2]) + \"%\", 6) + pad(interval(\"closed\", b), 18) + pad(interval(\"half-open\", b), 18) + pad(interval(\"open\", b), 18) + interval(\"left-open\", b)) ^0\n        k + 1 => k\n\ndef names(ks):\n    if len(ks) == 0:\n        return \"none\"\n    \"\" => out\n    for k in ks:\n        if out == \"\":\n            str(k) => out\n        else:\n            out + \" and \" + str(k) => out\n    return out\n\ndef one_income():\n    plain(input(\"income (0 to 9999999)> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return\n    if len(t) > 7:\n        \"Type a whole number from 0 to 9999999.\" ^0\n        return\n    for c in t:\n        if c < \"0\" or c > \"9\":\n            \"Type a whole number from 0 to 9999999.\" ^0\n            return\n    int(t) => x\n    show_income(x)\n\ndef show_income(x):\n    (\"Income \" + str(x) + \":\") ^0\n    (\"  \" + pad(\"reading\", 11) + pad(\"bands that take it\", 20) + \"slab tax: the whole income at the band's rate\") ^0\n    for r in bands.readings():\n        bands.taking(r, x) => ks\n        money(bands.slab(r, x)) => s\n        if len(ks) == 0:\n            s + \" - no band takes it, and the else branch charges the top rate\" => s\n        elif len(ks) > 1:\n            s + \" - band \" + str(ks[0]) + \", the first that takes it\" => s\n        (\"  \" + pad(r, 11) + pad(names(ks), 20) + s) ^0\n    (\"Marginal tax, each slice at its own band's rate: \" + money(bands.marginal(x)) + \" under every reading: a slice's width does not depend on which band owns its end.\") ^0\n\ndef partition(xs):\n    (\"Over the \" + str(len(xs)) + \" incomes on file:\") ^0\n    (\"  \" + pad(\"reading\", 11) + \"in no band  in one  in two   slab tax in all\") ^0\n    \"\" => exact\n    for r in bands.readings():\n        0 => none\n        0 => one\n        0 => two\n        0 => total\n        for x in xs:\n            len(bands.taking(r, x)) => n\n            if n == 0:\n                none + 1 => none\n            elif n == 1:\n                one + 1 => one\n            else:\n                two + 1 => two\n            total + bands.slab(r, x) => total\n        (\"  \" + pad(r, 11) + lpad(str(none), 10) + lpad(str(one), 8) + lpad(str(two), 8) + lpad(money(total), 18)) ^0\n        if one == len(xs):\n            if exact == \"\":\n                r => exact\n            else:\n                exact + \" and \" + r => exact\n    \"\" => edge\n    [] => seen\n    for x in xs:\n        bands.taking(\"closed\", x) => a\n        bands.taking(\"open\", x) => b\n        False => dup\n        for y in seen:\n            if y == x:\n                True => dup\n        if (len(a) != 1 or len(b) != 1) and not dup:\n            seen + [x] => seen\n            if edge == \"\":\n                str(x) => edge\n            else:\n                edge + \", \" + str(x) => edge\n    if edge != \"\":\n        (\"The readings part only at \" + edge + \": incomes on a band's end.\") ^0\n    (\"Every income in exactly one band: \" + exact + \".\") ^0\n    0 => m\n    for x in xs:\n        m + bands.marginal(x) => m\n    (\"Marginal tax in all: \" + money(m) + \", the same under every reading.\") ^0\n\ndef add(xs):\n    plain(input(\"income (0 to 9999999)> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return xs\n    if len(t) > 7:\n        \"Type a whole number from 0 to 9999999.\" ^0\n        return xs\n    for c in t:\n        if c < \"0\" or c > \"9\":\n            \"Type a whole number from 0 to 9999999.\" ^0\n            return xs\n    int(t) => x\n    (\"Income \" + str(x) + \" added; \" + str(len(xs) + 1) + \" on file.\") ^0\n    return xs + [x]\n\nbands.incomes() => xs\n\n\"== Tax brackets ==\" ^0\n\"Bands written from a sentence, read four ways, and the incomes that sit on their ends.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) bands  2) one income  3) the incomes on file  4) add an income  5) quit\" ^0\n    plain(input(\"choice> \")) => choice\n    if choice == \"1\":\n        show_bands()\n    elif choice == \"2\":\n        one_income()\n    elif choice == \"3\":\n        partition(xs)\n    elif choice == \"4\":\n        add(xs) => xs\n    elif choice == \"5\":\n        False => running\n    else:\n        \"Pick a number from 1 to 5.\" ^0\n\"Bye.\" ^0\n",
      "python": "import bands\n\ndef plain(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 pad(s, n):\n    out = s\n    while len(out) < n:\n        out = out + \" \"\n    return out\n\ndef lpad(s, n):\n    out = s\n    while len(out) < n:\n        out = \" \" + out\n    return out\n\ndef money(c):\n    p = c % 100\n    if p < 10:\n        return str(int((c - p) / 100)) + \".0\" + str(p)\n    return str(int((c - p) / 100)) + \".\" + str(p)\n\ndef interval(reading, b):\n    if reading == \"closed\" or reading == \"half-open\":\n        left = \"[\"\n    else:\n        left = \"(\"\n    if b[1] < 0:\n        return left + str(b[0]) + \", and up)\"\n    if reading == \"closed\" or reading == \"left-open\":\n        right = \"]\"\n    else:\n        right = \")\"\n    return left + str(b[0]) + \", \" + str(b[1]) + right\n\ndef show_bands():\n    print(\"The sentence: 0-10000 at 0%, 10000-40000 at 10%, 40000-100000 at 20%, 100000 and above at 30%.\")\n    print(\"  \" + pad(\"band\", 6) + pad(\"closed\", 18) + pad(\"half-open\", 18) + pad(\"open\", 18) + \"left-open\")\n    k = 1\n    for b in bands.bands():\n        print(\"  \" + pad(str(k) + \" \" + str(b[2]) + \"%\", 6) + pad(interval(\"closed\", b), 18) + pad(interval(\"half-open\", b), 18) + pad(interval(\"open\", b), 18) + interval(\"left-open\", b))\n        k = k + 1\n\ndef names(ks):\n    if len(ks) == 0:\n        return \"none\"\n    out = \"\"\n    for k in ks:\n        if out == \"\":\n            out = str(k)\n        else:\n            out = out + \" and \" + str(k)\n    return out\n\ndef one_income():\n    t = plain(input(\"income (0 to 9999999)> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return\n    if len(t) > 7:\n        print(\"Type a whole number from 0 to 9999999.\")\n        return\n    for c in t:\n        if c < \"0\" or c > \"9\":\n            print(\"Type a whole number from 0 to 9999999.\")\n            return\n    x = int(t)\n    show_income(x)\n\ndef show_income(x):\n    print(\"Income \" + str(x) + \":\")\n    print(\"  \" + pad(\"reading\", 11) + pad(\"bands that take it\", 20) + \"slab tax: the whole income at the band's rate\")\n    for r in bands.readings():\n        ks = bands.taking(r, x)\n        s = money(bands.slab(r, x))\n        if len(ks) == 0:\n            s = s + \" - no band takes it, and the else branch charges the top rate\"\n        elif len(ks) > 1:\n            s = s + \" - band \" + str(ks[0]) + \", the first that takes it\"\n        print(\"  \" + pad(r, 11) + pad(names(ks), 20) + s)\n    print(\"Marginal tax, each slice at its own band's rate: \" + money(bands.marginal(x)) + \" under every reading: a slice's width does not depend on which band owns its end.\")\n\ndef partition(xs):\n    print(\"Over the \" + str(len(xs)) + \" incomes on file:\")\n    print(\"  \" + pad(\"reading\", 11) + \"in no band  in one  in two   slab tax in all\")\n    exact = \"\"\n    for r in bands.readings():\n        none = 0\n        one = 0\n        two = 0\n        total = 0\n        for x in xs:\n            n = len(bands.taking(r, x))\n            if n == 0:\n                none = none + 1\n            elif n == 1:\n                one = one + 1\n            else:\n                two = two + 1\n            total = total + bands.slab(r, x)\n        print(\"  \" + pad(r, 11) + lpad(str(none), 10) + lpad(str(one), 8) + lpad(str(two), 8) + lpad(money(total), 18))\n        if one == len(xs):\n            if exact == \"\":\n                exact = r\n            else:\n                exact = exact + \" and \" + r\n    edge = \"\"\n    seen = []\n    for x in xs:\n        a = bands.taking(\"closed\", x)\n        b = bands.taking(\"open\", x)\n        dup = False\n        for y in seen:\n            if y == x:\n                dup = True\n        if (len(a) != 1 or len(b) != 1) and not dup:\n            seen = seen + [x]\n            if edge == \"\":\n                edge = str(x)\n            else:\n                edge = edge + \", \" + str(x)\n    if edge != \"\":\n        print(\"The readings part only at \" + edge + \": incomes on a band's end.\")\n    print(\"Every income in exactly one band: \" + exact + \".\")\n    m = 0\n    for x in xs:\n        m = m + bands.marginal(x)\n    print(\"Marginal tax in all: \" + money(m) + \", the same under every reading.\")\n\ndef add(xs):\n    t = plain(input(\"income (0 to 9999999)> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return xs\n    if len(t) > 7:\n        print(\"Type a whole number from 0 to 9999999.\")\n        return xs\n    for c in t:\n        if c < \"0\" or c > \"9\":\n            print(\"Type a whole number from 0 to 9999999.\")\n            return xs\n    x = int(t)\n    print(\"Income \" + str(x) + \" added; \" + str(len(xs) + 1) + \" on file.\")\n    return xs + [x]\n\nxs = bands.incomes()\nprint(\"== Tax brackets ==\")\nprint(\"Bands written from a sentence, read four ways, and the incomes that sit on their ends.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) bands  2) one income  3) the incomes on file  4) add an income  5) quit\")\n    choice = plain(input(\"choice> \"))\n    if choice == \"1\":\n        show_bands()\n    elif choice == \"2\":\n        one_income()\n    elif choice == \"3\":\n        partition(xs)\n    elif choice == \"4\":\n        xs = add(xs)\n    elif choice == \"5\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 5.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "bands.eml",
      "eml": "# P151 tax brackets - income bands written from a sentence, \"0-10000,\n# 10000-40000, 40000-100000, 100000 and above\", read four ways. As in the\n# corpus case boundary-inclusive-or-not, \"between\" is four questions -\n# closed, half-open, open, left-open - and they differ only at the end\n# points, which is why the difference survives every test in the middle.\n# Consecutive bands read closed take a boundary twice and read open take it\n# not at all; only half-open makes the bands a partition. A band is [low,\n# high, rate in percent]; the last has no high.\n\ndef bands():\n    return [[0, 10000, 0], [10000, 40000, 10], [40000, 100000, 20], [100000, 0 - 1, 30]]\n\ndef readings():\n    return [\"closed\", \"half-open\", \"open\", \"left-open\"]\n\ndef incomes():\n    return [0, 8500, 10000, 25000, 40000, 40001, 72000, 100000, 150000]\n\ndef takes(reading, b, x):\n    # does band b take income x under the reading\n    if reading == \"closed\" or reading == \"half-open\":\n        x >= b[0] => low_ok\n    else:\n        x > b[0] => low_ok\n    if b[1] < 0:\n        return low_ok\n    if reading == \"closed\" or reading == \"left-open\":\n        x <= b[1] => high_ok\n    else:\n        x < b[1] => high_ok\n    return low_ok and high_ok\n\ndef taking(reading, x):\n    # the numbers of the bands that take x, from 1\n    [] => out\n    1 => k\n    for b in bands():\n        if takes(reading, b, x):\n            out + [k] => out\n        k + 1 => k\n    return out\n\ndef slab(reading, x):\n    # the whole income at the rate of the first band that takes it, in\n    # cents; with no band the else branch charges the top rate\n    for b in bands():\n        if takes(reading, b, x):\n            return x * b[2]\n    return x * bands()[len(bands()) - 1][2]\n\ndef marginal(x):\n    # each slice of the income at its own band's rate, in cents\n    0 => t\n    for b in bands():\n        x => top\n        if b[1] >= 0 and b[1] < top:\n            b[1] => top\n        if top > b[0]:\n            t + (top - b[0]) * b[2] => t\n    return t\n",
      "python": "def bands():\n    return [[0, 10000, 0], [10000, 40000, 10], [40000, 100000, 20], [100000, 0 - 1, 30]]\n\ndef readings():\n    return [\"closed\", \"half-open\", \"open\", \"left-open\"]\n\ndef incomes():\n    return [0, 8500, 10000, 25000, 40000, 40001, 72000, 100000, 150000]\n\ndef takes(reading, b, x):\n    if reading == \"closed\" or reading == \"half-open\":\n        low_ok = x >= b[0]\n    else:\n        low_ok = x > b[0]\n    if b[1] < 0:\n        return low_ok\n    if reading == \"closed\" or reading == \"left-open\":\n        high_ok = x <= b[1]\n    else:\n        high_ok = x < b[1]\n    return low_ok and high_ok\n\ndef taking(reading, x):\n    out = []\n    k = 1\n    for b in bands():\n        if takes(reading, b, x):\n            out = out + [k]\n        k = k + 1\n    return out\n\ndef slab(reading, x):\n    for b in bands():\n        if takes(reading, b, x):\n            return x * b[2]\n    return x * bands()[len(bands()) - 1][2]\n\ndef marginal(x):\n    t = 0\n    for b in bands():\n        top = x\n        if b[1] >= 0 and b[1] < top:\n            top = b[1]\n        if top > b[0]:\n            t = t + (top - b[0]) * b[2]\n    return t\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n2\n\n2\n12345678\n2\n1e5\n2\n-5\n4\n\n4\nx\n4\n99999\n3\n5\n",
      "screen": "== Tax brackets ==\nBands written from a sentence, read four ways, and the incomes that sit on their ends.\n\n1) bands  2) one income  3) the incomes on file  4) add an income  5) quit\nchoice> 0\nPick a number from 1 to 5.\n\n1) bands  2) one income  3) the incomes on file  4) add an income  5) quit\nchoice> x\nPick a number from 1 to 5.\n\n1) bands  2) one income  3) the incomes on file  4) add an income  5) quit\nchoice> 2\nincome (0 to 9999999)> \nCancelled.\n\n1) bands  2) one income  3) the incomes on file  4) add an income  5) quit\nchoice> 2\nincome (0 to 9999999)> 12345678\nType a whole number from 0 to 9999999.\n\n1) bands  2) one income  3) the incomes on file  4) add an income  5) quit\nchoice> 2\nincome (0 to 9999999)> 1e5\nType a whole number from 0 to 9999999.\n\n1) bands  2) one income  3) the incomes on file  4) add an income  5) quit\nchoice> 2\nincome (0 to 9999999)> -5\nType a whole number from 0 to 9999999.\n\n1) bands  2) one income  3) the incomes on file  4) add an income  5) quit\nchoice> 4\nincome (0 to 9999999)> \nCancelled.\n\n1) bands  2) one income  3) the incomes on file  4) add an income  5) quit\nchoice> 4\nincome (0 to 9999999)> x\nType a whole number from 0 to 9999999.\n\n1) bands  2) one income  3) the incomes on file  4) add an income  5) quit\nchoice> 4\nincome (0 to 9999999)> 99999\nIncome 99999 added; 10 on file.\n\n1) bands  2) one income  3) the incomes on file  4) add an income  5) quit\nchoice> 3\nOver the 10 incomes on file:\n  reading    in no band  in one  in two   slab tax in all\n  closed              0       7       3         113900.00\n  half-open           0      10       0         128900.00\n  open                4       6       0         134900.00\n  left-open           1       9       0         113900.00\nThe readings part only at 0, 10000, 40000, 100000: incomes on a band's end.\nEvery income in exactly one band: half-open.\nMarginal tax in all: 76900.00, the same under every reading.\n\n1) bands  2) one income  3) the incomes on file  4) add an income  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n2\n10000\n2\n25000\n2\n100000\n2\n0\n3\n4\n40000\n3\n5\n",
      "screen": "== Tax brackets ==\nBands written from a sentence, read four ways, and the incomes that sit on their ends.\n\n1) bands  2) one income  3) the incomes on file  4) add an income  5) quit\nchoice> 1\nThe sentence: 0-10000 at 0%, 10000-40000 at 10%, 40000-100000 at 20%, 100000 and above at 30%.\n  band  closed            half-open         open              left-open\n  1 0%  [0, 10000]        [0, 10000)        (0, 10000)        (0, 10000]\n  2 10% [10000, 40000]    [10000, 40000)    (10000, 40000)    (10000, 40000]\n  3 20% [40000, 100000]   [40000, 100000)   (40000, 100000)   (40000, 100000]\n  4 30% [100000, and up)  [100000, and up)  (100000, and up)  (100000, and up)\n\n1) bands  2) one income  3) the incomes on file  4) add an income  5) quit\nchoice> 2\nincome (0 to 9999999)> 10000\nIncome 10000:\n  reading    bands that take it  slab tax: the whole income at the band's rate\n  closed     1 and 2             0.00 - band 1, the first that takes it\n  half-open  2                   1000.00\n  open       none                3000.00 - no band takes it, and the else branch charges the top rate\n  left-open  1                   0.00\nMarginal tax, each slice at its own band's rate: 0.00 under every reading: a slice's width does not depend on which band owns its end.\n\n1) bands  2) one income  3) the incomes on file  4) add an income  5) quit\nchoice> 2\nincome (0 to 9999999)> 25000\nIncome 25000:\n  reading    bands that take it  slab tax: the whole income at the band's rate\n  closed     2                   2500.00\n  half-open  2                   2500.00\n  open       2                   2500.00\n  left-open  2                   2500.00\nMarginal tax, each slice at its own band's rate: 1500.00 under every reading: a slice's width does not depend on which band owns its end.\n\n1) bands  2) one income  3) the incomes on file  4) add an income  5) quit\nchoice> 2\nincome (0 to 9999999)> 100000\nIncome 100000:\n  reading    bands that take it  slab tax: the whole income at the band's rate\n  closed     3 and 4             20000.00 - band 3, the first that takes it\n  half-open  4                   30000.00\n  open       none                30000.00 - no band takes it, and the else branch charges the top rate\n  left-open  3                   20000.00\nMarginal tax, each slice at its own band's rate: 15000.00 under every reading: a slice's width does not depend on which band owns its end.\n\n1) bands  2) one income  3) the incomes on file  4) add an income  5) quit\nchoice> 2\nincome (0 to 9999999)> 0\nIncome 0:\n  reading    bands that take it  slab tax: the whole income at the band's rate\n  closed     1                   0.00\n  half-open  1                   0.00\n  open       none                0.00 - no band takes it, and the else branch charges the top rate\n  left-open  none                0.00 - no band takes it, and the else branch charges the top rate\nMarginal tax, each slice at its own band's rate: 0.00 under every reading: a slice's width does not depend on which band owns its end.\n\n1) bands  2) one income  3) the incomes on file  4) add an income  5) quit\nchoice> 3\nOver the 9 incomes on file:\n  reading    in no band  in one  in two   slab tax in all\n  closed              0       6       3          93900.20\n  half-open           0       9       0         108900.20\n  open                4       5       0         114900.20\n  left-open           1       8       0          93900.20\nThe readings part only at 0, 10000, 40000, 100000: incomes on a band's end.\nEvery income in exactly one band: half-open.\nMarginal tax in all: 61900.20, the same under every reading.\n\n1) bands  2) one income  3) the incomes on file  4) add an income  5) quit\nchoice> 4\nincome (0 to 9999999)> 40000\nIncome 40000 added; 10 on file.\n\n1) bands  2) one income  3) the incomes on file  4) add an income  5) quit\nchoice> 3\nOver the 10 incomes on file:\n  reading    in no band  in one  in two   slab tax in all\n  closed              0       6       4          97900.20\n  half-open           0      10       0         116900.20\n  open                5       5       0         126900.20\n  left-open           1       9       0          97900.20\nThe readings part only at 0, 10000, 40000, 100000: incomes on a band's end.\nEvery income in exactly one band: half-open.\nMarginal tax in all: 64900.20, the same under every reading.\n\n1) bands  2) one income  3) the incomes on file  4) add an income  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "boundary-inclusive-or-not",
      "caseId": "233-boundary-inclusive-or-not",
      "title": "Four readings of one sentence, differing on two values"
    }
  ],
  "updated": "2026-10-18"
}
