{
  "id": "P163",
  "slug": "invoice-rounding",
  "key": "P163-invoice-rounding",
  "title": "Invoice rounding",
  "summary": "A supplier's invoice with tax on every line, each line rounded half-up to the cent: rounded lines add up to more than the invoice is worth, and rounded shares of it to 101%.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P163 - Invoice rounding\n\nA supplier's invoice with 8.25% tax on every line, each line rounded\nhalf-up to the cent, and what the total is a sum of.\n\n- `main.eml` - the menu, the invoice, the month's invoices, the shares by\n  category, and a line of your own\n- `invoice.eml` - the tax, the lines and their categories, exact amounts\n  in ten-thousandths of a cent, rounding to the cent, the month's run and\n  whole-percent shares\n\nHow each part works:\n\n- Amounts are kept exactly, as whole ten-thousandths of a cent, so no\n  float is involved; a line is rounded half-up to the cent, as an invoice\n  must print it.\n- As in the corpus case `the-rounding-was-applied-at-every-step`, a total of\n  rounded lines carries one rounding per line. Invoice 1001 has six lines,\n  all six rounded up by 0.43 to 0.49 of a cent: rounded line by line it is\n  135.08, added exactly and rounded once 135.05.\n- As in `the-total-was-the-sum-of-the-rounded-rows`, the month's run of 300\n  invoices, 1,800 lines, comes to 23 cents more line by line than added\n  exactly: 878 lines rounded up and 922 down, and the errors do not cancel\n  out. No line is off by more than half a cent; 1,800 lines could drift as\n  far as 9.00.\n- As in `the-shares-were-rounded-and-summed-to-101`, the invoice's three\n  categories are 44.70%, 24.59% and 30.71% of it; each rounded to a whole\n  percent, 45%, 25% and 31%, they add up to 101%.\n\nWhat is checked: menu choices 1 to 5; a quantity from 1 to 999; a unit\nprice written as 2, 2.4 or 2.49, from 0.01 to 999.99. An empty answer\ncancels.\n\nSessions: `sessions/basic.in` gives:\n- the invoice, the month's invoices and the shares;\n- lines of your own: 3 at 18.89 (rounded up), 1 at 1.00 (rounded down)\n  and 4 at 1.00 (a whole number of cents).\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- quantities: a cancel, 0, 1000 and x;\n- then prices with a cancel, 0, 1000, .5, 2.499 and x;\n- 2 at 2.5, rounded down by a quarter of a cent.\n\nBuilt on the verified corpus cases `the-rounding-was-applied-at-every-step`\n(twelve amounts rounded one by one against rounded once),\n`the-total-was-the-sum-of-the-rounded-rows` (90,000 rows correct to the\ncent, and a total 25 cents off) and `the-shares-were-rounded-and-summed-to-101`\n(three shares, each rounded correctly, adding up to 101).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P163 invoice rounding: one invoice rounded line by line and rounded once,\n# the month's run of invoices, the shares by category, and a line of your\n# own.\nimport invoice\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 digits(t):\n    for c in t:\n        if c < \"0\" or c > \"9\":\n            return False\n    return len(t) > 0\n\ndef commas(n):\n    str(n) => s\n    \"\" => out\n    0 => k\n    len(s) => i\n    while i > 0:\n        if k == 3:\n            \",\" + out => out\n            0 => k\n        s[i - 1] + out => out\n        k + 1 => k\n        i - 1 => i\n    return out\n\ndef fraction(n, places):\n    # n as exactly that many decimal places, n >= 0\n    str(n) => s\n    while len(s) < places:\n        \"0\" + s => s\n    return s\n\ndef money(c):\n    # c cents, c >= 0\n    return commas(int((c - c % 100) / 100)) + \".\" + fraction(c % 100, 2)\n\ndef exact_money(x):\n    # x ten-thousandths of a cent, as dollars with six places\n    return commas(int((x - x % 1000000) / 1000000)) + \".\" + fraction(x % 1000000, 6)\n\ndef part_of_cent(e):\n    # e ten-thousandths of a cent, e >= 0, as a part of a cent\n    return \"0.\" + fraction(e, 4)\n\ndef show_invoice():\n    \"Invoice 1001, with 8.25% tax on every line, in dollars:\" ^0\n    (\"  \" + pad(\"item\", 21) + lpad(\"qty\", 4) + lpad(\"price\", 9) + lpad(\"with tax, exact\", 18) + lpad(\"on the invoice\", 16)) ^0\n    0 => each\n    0 => whole\n    0 => up\n    for ln in invoice.lines():\n        invoice.exact(ln[2], ln[3]) => x\n        invoice.cents(x) => c\n        each + c => each\n        whole + x => whole\n        if c * 10000 > x:\n            up + 1 => up\n        (\"  \" + pad(ln[0], 21) + lpad(str(ln[2]), 4) + lpad(money(ln[3]), 9) + lpad(exact_money(x), 18) + lpad(money(c), 16)) ^0\n    (\"Rounding each line to the cent, then adding: \" + money(each) + \".\") ^0\n    (\"Adding the exact lines, then rounding once:  \" + money(invoice.cents(whole)) + \".\") ^0\n    (\"Every line is right to the cent, and \" + str(up) + \" of the \" + str(len(invoice.lines())) + \" were rounded up: the lines add up to \" + str(each - invoice.cents(whole)) + \" cents more than the invoice is worth.\") ^0\n\ndef show_run():\n    invoice.run() => t\n    invoice.run_size() * len(invoice.lines()) => n\n    (\"The month's run: \" + str(invoice.run_size()) + \" invoices of the same six items in varying quantities, \" + commas(n) + \" lines.\") ^0\n    (\"Rounding each line, then adding: \" + money(t[0]) + \".\") ^0\n    (\"Adding exactly, then rounding once: \" + money(invoice.cents(t[1])) + \".\") ^0\n    (str(t[2]) + \" lines were rounded up and \" + str(t[3]) + \" down.\") ^0\n    (\"The lines come to \" + str(t[0] - invoice.cents(t[1])) + \" cents more than the run is worth. No line is off by more than half a cent, and the errors do not cancel out: \" + commas(n) + \" lines could drift as far as \" + money(int(n / 2)) + \" either way.\") ^0\n\ndef show_shares():\n    [] => sums\n    0 => total\n    for cat in invoice.categories():\n        0 => v\n        for ln in invoice.lines():\n            if ln[1] == cat:\n                v + invoice.cents(invoice.exact(ln[2], ln[3])) => v\n        sums + [v] => sums\n        total + v => total\n    (\"Invoice 1001 by category, of \" + money(total) + \", each share rounded half-up to a whole percent:\") ^0\n    0 => s\n    0 => i\n    for cat in invoice.categories():\n        invoice.share_hundredths(sums[i], total) => h\n        invoice.share(sums[i], total) => p\n        s + p => s\n        (\"  \" + pad(cat, 8) + lpad(money(sums[i]), 8) + lpad(str(int((h - h % 100) / 100)) + \".\" + fraction(h % 100, 2) + \"%\", 10) + lpad(str(p) + \"%\", 6)) ^0\n        i + 1 => i\n    (\"The rounded shares add up to \" + str(s) + \"%, and the three categories are the whole invoice: each share is right to the percent, and their sum is not.\") ^0\n\ndef price(t):\n    # cents for a price typed as 2, 2.4 or 2.49; -1 if it is not one\n    0 => dot\n    0 => i\n    0 => at\n    for c in t:\n        if c == \".\":\n            dot + 1 => dot\n            i => at\n        elif c < \"0\" or c > \"9\":\n            return 0 - 1\n        i + 1 => i\n    if dot == 0:\n        if len(t) < 1 or len(t) > 3:\n            return 0 - 1\n        return int(t) * 100\n    if dot > 1 or at < 1 or at > 3 or len(t) - at - 1 < 1 or len(t) - at - 1 > 2:\n        return 0 - 1\n    t[at + 1:] => f\n    if len(f) == 1:\n        f + \"0\" => f\n    return int(t[0:at]) * 100 + int(f)\n\ndef own():\n    plain(input(\"quantity (1 to 999)> \")) => q\n    if q == \"\":\n        \"Cancelled.\" ^0\n        return\n    if len(q) > 3 or not digits(q) or int(q) < 1:\n        \"Type a whole number from 1 to 999.\" ^0\n        return\n    plain(input(\"unit price (0.01 to 999.99)> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return\n    price(t) => p\n    if p < 1:\n        \"Type a price such as 2.49, from 0.01 to 999.99.\" ^0\n        return\n    invoice.exact(int(q), p) => x\n    invoice.cents(x) => c\n    c * 10000 - x => e\n    (\"With tax: \" + exact_money(x) + \" exactly, \" + money(c) + \" on the invoice.\") ^0\n    if e > 0:\n        (\"Rounded up by \" + part_of_cent(e) + \" of a cent.\") ^0\n    elif e < 0:\n        (\"Rounded down by \" + part_of_cent(0 - e) + \" of a cent.\") ^0\n    else:\n        \"A whole number of cents: nothing to round.\" ^0\n    (\"Ten lines like it: \" + money(10 * c) + \" rounded line by line, \" + money(invoice.cents(10 * x)) + \" rounded once.\") ^0\n\n\"== Invoice rounding ==\" ^0\n\"Every line rounded to the cent, and what the total is a sum of.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) the invoice  2) the month's invoices  3) shares by category  4) a line of your own  5) quit\" ^0\n    plain(input(\"choice> \")) => choice\n    if choice == \"1\":\n        show_invoice()\n    elif choice == \"2\":\n        show_run()\n    elif choice == \"3\":\n        show_shares()\n    elif choice == \"4\":\n        own()\n    elif choice == \"5\":\n        False => running\n    else:\n        \"Pick a number from 1 to 5.\" ^0\n\"Bye.\" ^0\n",
      "python": "import invoice\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 digits(t):\n    for c in t:\n        if c < \"0\" or c > \"9\":\n            return False\n    return len(t) > 0\n\ndef commas(n):\n    s = str(n)\n    out = \"\"\n    k = 0\n    i = len(s)\n    while i > 0:\n        if k == 3:\n            out = \",\" + out\n            k = 0\n        out = s[i - 1] + out\n        k = k + 1\n        i = i - 1\n    return out\n\ndef fraction(n, places):\n    s = str(n)\n    while len(s) < places:\n        s = \"0\" + s\n    return s\n\ndef money(c):\n    return commas(int((c - c % 100) / 100)) + \".\" + fraction(c % 100, 2)\n\ndef exact_money(x):\n    return commas(int((x - x % 1000000) / 1000000)) + \".\" + fraction(x % 1000000, 6)\n\ndef part_of_cent(e):\n    return \"0.\" + fraction(e, 4)\n\ndef show_invoice():\n    print(\"Invoice 1001, with 8.25% tax on every line, in dollars:\")\n    print(\"  \" + pad(\"item\", 21) + lpad(\"qty\", 4) + lpad(\"price\", 9) + lpad(\"with tax, exact\", 18) + lpad(\"on the invoice\", 16))\n    each = 0\n    whole = 0\n    up = 0\n    for ln in invoice.lines():\n        x = invoice.exact(ln[2], ln[3])\n        c = invoice.cents(x)\n        each = each + c\n        whole = whole + x\n        if c * 10000 > x:\n            up = up + 1\n        print(\"  \" + pad(ln[0], 21) + lpad(str(ln[2]), 4) + lpad(money(ln[3]), 9) + lpad(exact_money(x), 18) + lpad(money(c), 16))\n    print(\"Rounding each line to the cent, then adding: \" + money(each) + \".\")\n    print(\"Adding the exact lines, then rounding once:  \" + money(invoice.cents(whole)) + \".\")\n    print(\"Every line is right to the cent, and \" + str(up) + \" of the \" + str(len(invoice.lines())) + \" were rounded up: the lines add up to \" + str(each - invoice.cents(whole)) + \" cents more than the invoice is worth.\")\n\ndef show_run():\n    t = invoice.run()\n    n = invoice.run_size() * len(invoice.lines())\n    print(\"The month's run: \" + str(invoice.run_size()) + \" invoices of the same six items in varying quantities, \" + commas(n) + \" lines.\")\n    print(\"Rounding each line, then adding: \" + money(t[0]) + \".\")\n    print(\"Adding exactly, then rounding once: \" + money(invoice.cents(t[1])) + \".\")\n    print(str(t[2]) + \" lines were rounded up and \" + str(t[3]) + \" down.\")\n    print(\"The lines come to \" + str(t[0] - invoice.cents(t[1])) + \" cents more than the run is worth. No line is off by more than half a cent, and the errors do not cancel out: \" + commas(n) + \" lines could drift as far as \" + money(int(n / 2)) + \" either way.\")\n\ndef show_shares():\n    sums = []\n    total = 0\n    for cat in invoice.categories():\n        v = 0\n        for ln in invoice.lines():\n            if ln[1] == cat:\n                v = v + invoice.cents(invoice.exact(ln[2], ln[3]))\n        sums = sums + [v]\n        total = total + v\n    print(\"Invoice 1001 by category, of \" + money(total) + \", each share rounded half-up to a whole percent:\")\n    s = 0\n    i = 0\n    for cat in invoice.categories():\n        h = invoice.share_hundredths(sums[i], total)\n        p = invoice.share(sums[i], total)\n        s = s + p\n        print(\"  \" + pad(cat, 8) + lpad(money(sums[i]), 8) + lpad(str(int((h - h % 100) / 100)) + \".\" + fraction(h % 100, 2) + \"%\", 10) + lpad(str(p) + \"%\", 6))\n        i = i + 1\n    print(\"The rounded shares add up to \" + str(s) + \"%, and the three categories are the whole invoice: each share is right to the percent, and their sum is not.\")\n\ndef price(t):\n    dot = 0\n    i = 0\n    at = 0\n    for c in t:\n        if c == \".\":\n            dot = dot + 1\n            at = i\n        elif c < \"0\" or c > \"9\":\n            return 0 - 1\n        i = i + 1\n    if dot == 0:\n        if len(t) < 1 or len(t) > 3:\n            return 0 - 1\n        return int(t) * 100\n    if dot > 1 or at < 1 or at > 3 or len(t) - at - 1 < 1 or len(t) - at - 1 > 2:\n        return 0 - 1\n    f = t[at + 1:]\n    if len(f) == 1:\n        f = f + \"0\"\n    return int(t[0:at]) * 100 + int(f)\n\ndef own():\n    q = plain(input(\"quantity (1 to 999)> \"))\n    if q == \"\":\n        print(\"Cancelled.\")\n        return\n    if len(q) > 3 or not digits(q) or int(q) < 1:\n        print(\"Type a whole number from 1 to 999.\")\n        return\n    t = plain(input(\"unit price (0.01 to 999.99)> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return\n    p = price(t)\n    if p < 1:\n        print(\"Type a price such as 2.49, from 0.01 to 999.99.\")\n        return\n    x = invoice.exact(int(q), p)\n    c = invoice.cents(x)\n    e = c * 10000 - x\n    print(\"With tax: \" + exact_money(x) + \" exactly, \" + money(c) + \" on the invoice.\")\n    if e > 0:\n        print(\"Rounded up by \" + part_of_cent(e) + \" of a cent.\")\n    elif e < 0:\n        print(\"Rounded down by \" + part_of_cent(0 - e) + \" of a cent.\")\n    else:\n        print(\"A whole number of cents: nothing to round.\")\n    print(\"Ten lines like it: \" + money(10 * c) + \" rounded line by line, \" + money(invoice.cents(10 * x)) + \" rounded once.\")\n\nprint(\"== Invoice rounding ==\")\nprint(\"Every line rounded to the cent, and what the total is a sum of.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) the invoice  2) the month's invoices  3) shares by category  4) a line of your own  5) quit\")\n    choice = plain(input(\"choice> \"))\n    if choice == \"1\":\n        show_invoice()\n    elif choice == \"2\":\n        show_run()\n    elif choice == \"3\":\n        show_shares()\n    elif choice == \"4\":\n        own()\n    elif choice == \"5\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 5.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "invoice.eml",
      "eml": "# P163 invoice rounding - a supplier's invoice with 8.25% tax on every line,\n# kept exactly in ten-thousandths of a cent. As in the corpus cases\n# the-rounding-was-applied-at-every-step and\n# the-total-was-the-sum-of-the-rounded-rows, every line is rounded half-up to\n# the cent, which is right, and a total made of rounded lines carries one\n# rounding per line where the exact total rounded once carries one. And as in\n# the-shares-were-rounded-and-summed-to-101, shares rounded one by one need\n# not add up to the whole.\n\ndef tax():\n    # 8.25%, as 10825 per 10000 of the price with tax\n    return 10825\n\ndef lines():\n    # [item, category, quantity, unit price in cents]\n    return [[\"espresso beans 1 kg\", \"coffee\", 2, 1779], [\"decaf beans 1 kg\", \"coffee\", 1, 2019],\n            [\"whole milk 2 l\", \"dairy\", 8, 269], [\"oat milk 1 l\", \"dairy\", 4, 229],\n            [\"croissants\", \"bakery\", 14, 139], [\"muffins\", \"bakery\", 13, 145]]\n\ndef categories():\n    return [\"coffee\", \"dairy\", \"bakery\"]\n\ndef exact(q, p):\n    # a line with tax, in ten-thousandths of a cent\n    return q * p * tax()\n\ndef cents(x):\n    # x ten-thousandths of a cent, rounded half-up to the cent\n    x + 5000 => y\n    return int((y - y % 10000) / 10000)\n\ndef run_size():\n    return 300\n\ndef run_quantity(k, i):\n    # the quantity of line i on invoice k of the month's run\n    lines()[i][2] => q\n    return 1 + (k * (i + 2) + i) % (2 * q)\n\ndef run():\n    # [rounded line by line, exact sum, lines rounded up, lines rounded down]\n    [0, 0, 0, 0] => t\n    1 => k\n    while k <= run_size():\n        0 => i\n        for ln in lines():\n            exact(run_quantity(k, i), ln[3]) => x\n            t[0] + cents(x) => t[0]\n            t[1] + x => t[1]\n            if cents(x) * 10000 > x:\n                t[2] + 1 => t[2]\n            if cents(x) * 10000 < x:\n                t[3] + 1 => t[3]\n            i + 1 => i\n        k + 1 => k\n    return t\n\ndef share(v, total):\n    # v of total as a whole percent, rounded half-up\n    v * 200 + total => y\n    return int((y - y % (2 * total)) / (2 * total))\n\ndef share_hundredths(v, total):\n    # v of total in hundredths of a percent, rounded half-up\n    v * 20000 + total => y\n    return int((y - y % (2 * total)) / (2 * total))\n",
      "python": "def tax():\n    return 10825\n\ndef lines():\n    return [[\"espresso beans 1 kg\", \"coffee\", 2, 1779], [\"decaf beans 1 kg\", \"coffee\", 1, 2019], [\"whole milk 2 l\", \"dairy\", 8, 269], [\"oat milk 1 l\", \"dairy\", 4, 229], [\"croissants\", \"bakery\", 14, 139], [\"muffins\", \"bakery\", 13, 145]]\n\ndef categories():\n    return [\"coffee\", \"dairy\", \"bakery\"]\n\ndef exact(q, p):\n    return q * p * tax()\n\ndef cents(x):\n    y = x + 5000\n    return int((y - y % 10000) / 10000)\n\ndef run_size():\n    return 300\n\ndef run_quantity(k, i):\n    q = lines()[i][2]\n    return 1 + (k * (i + 2) + i) % (2 * q)\n\ndef run():\n    t = [0, 0, 0, 0]\n    k = 1\n    while k <= run_size():\n        i = 0\n        for ln in lines():\n            x = exact(run_quantity(k, i), ln[3])\n            t[0] = t[0] + cents(x)\n            t[1] = t[1] + x\n            if cents(x) * 10000 > x:\n                t[2] = t[2] + 1\n            if cents(x) * 10000 < x:\n                t[3] = t[3] + 1\n            i = i + 1\n        k = k + 1\n    return t\n\ndef share(v, total):\n    y = v * 200 + total\n    return int((y - y % (2 * total)) / (2 * total))\n\ndef share_hundredths(v, total):\n    y = v * 20000 + total\n    return int((y - y % (2 * total)) / (2 * total))\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n4\n\n4\n0\n4\n1000\n4\nx\n4\n2\n\n4\n2\n0\n4\n2\n1000\n4\n2\n.5\n4\n2\n2.499\n4\n2\nx\n4\n2\n2.5\n5\n",
      "screen": "== Invoice rounding ==\nEvery line rounded to the cent, and what the total is a sum of.\n\n1) the invoice  2) the month's invoices  3) shares by category  4) a line of your own  5) quit\nchoice> 0\nPick a number from 1 to 5.\n\n1) the invoice  2) the month's invoices  3) shares by category  4) a line of your own  5) quit\nchoice> x\nPick a number from 1 to 5.\n\n1) the invoice  2) the month's invoices  3) shares by category  4) a line of your own  5) quit\nchoice> 4\nquantity (1 to 999)> \nCancelled.\n\n1) the invoice  2) the month's invoices  3) shares by category  4) a line of your own  5) quit\nchoice> 4\nquantity (1 to 999)> 0\nType a whole number from 1 to 999.\n\n1) the invoice  2) the month's invoices  3) shares by category  4) a line of your own  5) quit\nchoice> 4\nquantity (1 to 999)> 1000\nType a whole number from 1 to 999.\n\n1) the invoice  2) the month's invoices  3) shares by category  4) a line of your own  5) quit\nchoice> 4\nquantity (1 to 999)> x\nType a whole number from 1 to 999.\n\n1) the invoice  2) the month's invoices  3) shares by category  4) a line of your own  5) quit\nchoice> 4\nquantity (1 to 999)> 2\nunit price (0.01 to 999.99)> \nCancelled.\n\n1) the invoice  2) the month's invoices  3) shares by category  4) a line of your own  5) quit\nchoice> 4\nquantity (1 to 999)> 2\nunit price (0.01 to 999.99)> 0\nType a price such as 2.49, from 0.01 to 999.99.\n\n1) the invoice  2) the month's invoices  3) shares by category  4) a line of your own  5) quit\nchoice> 4\nquantity (1 to 999)> 2\nunit price (0.01 to 999.99)> 1000\nType a price such as 2.49, from 0.01 to 999.99.\n\n1) the invoice  2) the month's invoices  3) shares by category  4) a line of your own  5) quit\nchoice> 4\nquantity (1 to 999)> 2\nunit price (0.01 to 999.99)> .5\nType a price such as 2.49, from 0.01 to 999.99.\n\n1) the invoice  2) the month's invoices  3) shares by category  4) a line of your own  5) quit\nchoice> 4\nquantity (1 to 999)> 2\nunit price (0.01 to 999.99)> 2.499\nType a price such as 2.49, from 0.01 to 999.99.\n\n1) the invoice  2) the month's invoices  3) shares by category  4) a line of your own  5) quit\nchoice> 4\nquantity (1 to 999)> 2\nunit price (0.01 to 999.99)> x\nType a price such as 2.49, from 0.01 to 999.99.\n\n1) the invoice  2) the month's invoices  3) shares by category  4) a line of your own  5) quit\nchoice> 4\nquantity (1 to 999)> 2\nunit price (0.01 to 999.99)> 2.5\nWith tax: 5.412500 exactly, 5.41 on the invoice.\nRounded down by 0.2500 of a cent.\nTen lines like it: 54.10 rounded line by line, 54.13 rounded once.\n\n1) the invoice  2) the month's invoices  3) shares by category  4) a line of your own  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n2\n3\n4\n3\n18.89\n4\n1\n1\n4\n4\n1\n5\n",
      "screen": "== Invoice rounding ==\nEvery line rounded to the cent, and what the total is a sum of.\n\n1) the invoice  2) the month's invoices  3) shares by category  4) a line of your own  5) quit\nchoice> 1\nInvoice 1001, with 8.25% tax on every line, in dollars:\n  item                  qty    price   with tax, exact  on the invoice\n  espresso beans 1 kg     2    17.79         38.515350           38.52\n  decaf beans 1 kg        1    20.19         21.855675           21.86\n  whole milk 2 l          8     2.69         23.295400           23.30\n  oat milk 1 l            4     2.29          9.915700            9.92\n  croissants             14     1.39         21.065450           21.07\n  muffins                13     1.45         20.405125           20.41\nRounding each line to the cent, then adding: 135.08.\nAdding the exact lines, then rounding once:  135.05.\nEvery line is right to the cent, and 6 of the 6 were rounded up: the lines add up to 3 cents more than the invoice is worth.\n\n1) the invoice  2) the month's invoices  3) shares by category  4) a line of your own  5) quit\nchoice> 2\nThe month's run: 300 invoices of the same six items in varying quantities, 1,800 lines.\nRounding each line, then adding: 45,266.61.\nAdding exactly, then rounding once: 45,266.38.\n878 lines were rounded up and 922 down.\nThe lines come to 23 cents more than the run is worth. No line is off by more than half a cent, and the errors do not cancel out: 1,800 lines could drift as far as 9.00 either way.\n\n1) the invoice  2) the month's invoices  3) shares by category  4) a line of your own  5) quit\nchoice> 3\nInvoice 1001 by category, of 135.08, each share rounded half-up to a whole percent:\n  coffee     60.38    44.70%   45%\n  dairy      33.22    24.59%   25%\n  bakery     41.48    30.71%   31%\nThe rounded shares add up to 101%, and the three categories are the whole invoice: each share is right to the percent, and their sum is not.\n\n1) the invoice  2) the month's invoices  3) shares by category  4) a line of your own  5) quit\nchoice> 4\nquantity (1 to 999)> 3\nunit price (0.01 to 999.99)> 18.89\nWith tax: 61.345275 exactly, 61.35 on the invoice.\nRounded up by 0.4725 of a cent.\nTen lines like it: 613.50 rounded line by line, 613.45 rounded once.\n\n1) the invoice  2) the month's invoices  3) shares by category  4) a line of your own  5) quit\nchoice> 4\nquantity (1 to 999)> 1\nunit price (0.01 to 999.99)> 1\nWith tax: 1.082500 exactly, 1.08 on the invoice.\nRounded down by 0.2500 of a cent.\nTen lines like it: 10.80 rounded line by line, 10.83 rounded once.\n\n1) the invoice  2) the month's invoices  3) shares by category  4) a line of your own  5) quit\nchoice> 4\nquantity (1 to 999)> 4\nunit price (0.01 to 999.99)> 1\nWith tax: 4.330000 exactly, 4.33 on the invoice.\nA whole number of cents: nothing to round.\nTen lines like it: 43.30 rounded line by line, 43.30 rounded once.\n\n1) the invoice  2) the month's invoices  3) shares by category  4) a line of your own  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "the-rounding-was-applied-at-every-step",
      "caseId": "471-the-rounding-was-applied-at-every-step",
      "title": "The rounding was applied at every step"
    },
    {
      "slug": "the-total-was-the-sum-of-the-rounded-rows",
      "caseId": "804-the-total-was-the-sum-of-the-rounded-rows",
      "title": "The total was the sum of the rounded rows"
    },
    {
      "slug": "the-shares-were-rounded-and-summed-to-101",
      "caseId": "817-the-shares-were-rounded-and-summed-to-101",
      "title": "The shares were rounded and summed to 101"
    }
  ],
  "updated": "2026-10-19"
}
