Project P104

Money ledger

A ledger kept in whole cents beside the same sums added as floats; one amount added many times; tax on every line under four rounding rules and the drift each one leaves over many halves; and an amount shared by weights three ways, two of which always add up.

2 modules · 2 recorded sessionstext-menu UI in the terminalupdated 2026-10-16

Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.

About

A ledger kept in whole cents, next to the same sums added as floats. The program also adds one amount many times, works out tax on every line under four rounding rules, and shares an amount by weights so that the parts add up to it.

  • main.eml - the menu, the ledger table, the repeated amount, the tax table, sharing and entries
  • money.eml - amounts in cents and their text, float and exact totals, the four rounding rules, and the three ways of sharing

How each part works:

  • Money is whole cents. Typed amounts have at most two decimals, and nothing is ever a float except the float column itself.
  • Floats, as in the corpus case money-float-accumulation: the same entries added as floats can miss the exact total. Ten additions of 0.01 are already off, and the float total comes out low for one count and high for the next. A report with two decimals hides the difference: it prints 0.00, or -0.00.
  • Tax on every line: each line's tax is an exact fraction of a cent, rounded by one of four rules: - half-up: a half away from zero; - half-even: a half to the even cent; - half-down: a half toward zero; - truncate: the fraction dropped.

The program shows the lines rounded and then added, how far that is from the exact tax, and the total rounded once. - The ledger of halves: 21 lines of 0.01, 0.03 ... 0.41. At 50% every line's tax ends in exactly half a cent. As in the corpus case rounding-rule-drift, the rules that send every half the same way drift by 10.5 cents, and half-even by half a cent. - Sharing by weights, three ways: - every part rounded on its own, which can lose or invent a cent; - the largest remainders: each part rounded down, then the cents left over go to the largest remainders, the earlier part first on a tie; - the running total rounded, each part being the difference.

The last two always add up to the amount, but can give the leftover cent to different people.

What is checked: menu choices 1 to 7; an amount with at most two decimals, up to 9,999,999.99; a description of up to 12 characters, and 30 entries at most; a tax rate from 0.01% to 100%; 2 to 8 weights, each a whole number from 1 to 9999. An empty answer cancels.

Sessions: sessions/basic.in gives: - the starting ledger, whose float total is 56.39000000000001 against an exact 56.39; - 0.01, 0.10 and 19.99 added 10, 100, 1,000 and 2,000 times; - 8.25% tax on the ledger; - the ledger of halves at 50%; - 100.00 shared 1 1 1, 0.05 shared four ways, and 10.00 shared seven ways, where rounding each part invents a cent; - an empty ledger, rent 850.00 and a deposit of -850.00.

sessions/bad-input.in gives: - menu choices 0 and x; - entries: a cancel, a description too long, amounts with three decimals, a comma and too many digits, then a cancel; - the repeated amount 0, a cancel and abc; - tax rates 0, 100.01 and abc, then a cancel; - sharing: 0, -3, one weight, a weight of 0, nine weights, and a cancel; - a new ledger of the wrong kind, then a cancel.

Built on the verified corpus cases money-float-accumulation (floats are already off at ten items, on no stable side) and rounding-rule-drift (a rule that sends every half one way drifts by half the number of ties).

Recorded sessions

What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.

bad-input

interpreter: byte-equal
== Money ledger ==
Money kept in whole cents, next to the same sums added as floats; rounding rules for fractions of a cent; and sharing an amount so the parts add up.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 0
Pick a number from 1 to 7.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> x
Pick a number from 1 to 7.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 2
what (up to 12 letters)> 
Cancelled.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 2
what (up to 12 letters)> a very long description
Keep it to 12 characters.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 2
what (up to 12 letters)> tea
amount> 12.345
Type an amount such as 12.34 or -5, at most two decimals and 9,999,999.99.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 2
what (up to 12 letters)> tea
amount> 1,50
Type an amount such as 12.34 or -5, at most two decimals and 9,999,999.99.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 2
what (up to 12 letters)> tea
amount> 10000000
Type an amount such as 12.34 or -5, at most two decimals and 9,999,999.99.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 2
what (up to 12 letters)> tea
amount> 
Cancelled.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 3
amount> 0
Pick an amount that is not zero.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 3
amount> 
Cancelled.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 3
amount> abc
Type an amount such as 12.34 or -5, at most two decimals and 9,999,999.99.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 4
tax rate in percent (0.01 to 100)> 0
Type a rate from 0.01 to 100, at most two decimals.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 4
tax rate in percent (0.01 to 100)> 100.01
Type a rate from 0.01 to 100, at most two decimals.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 4
tax rate in percent (0.01 to 100)> abc
Type a rate from 0.01 to 100, at most two decimals.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 4
tax rate in percent (0.01 to 100)> 
Cancelled.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 5
amount to share> 0
Share an amount above zero.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 5
amount to share> -3
Share an amount above zero.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 5
amount to share> 10
weights, whole numbers such as 1 1 1 or 50 30 20> 1
Type 2 to 8 whole numbers from 1 to 9999.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 5
amount to share> 10
weights, whole numbers such as 1 1 1 or 50 30 20> 1 0 1
Type 2 to 8 whole numbers from 1 to 9999.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 5
amount to share> 10
weights, whole numbers such as 1 1 1 or 50 30 20> 1 2 3 4 5 6 7 8 9
Type 2 to 8 whole numbers from 1 to 9999.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 5
amount to share> 10
weights, whole numbers such as 1 1 1 or 50 30 20> 
Cancelled.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 6
empty, or halves (21 lines of 0.01, 0.03 ... 0.41)> full
Type empty or halves.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 6
empty, or halves (21 lines of 0.01, 0.03 ... 0.41)> 
Cancelled.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 7
Bye.
What was typed (53 lines)
0
x
2

2
a very long description
2
tea
12.345
2
tea
1,50
2
tea
10000000
2
tea

3
0
3

3
abc
4
0
4
100.01
4
abc
4

5
0
5
-3
5
10
1
5
10
1 0 1
5
10
1 2 3 4 5 6 7 8 9
5
10

6
full
6

7

basic

interpreter: byte-equal
== Money ledger ==
Money kept in whole cents, next to the same sums added as floats; rounding rules for fractions of a cent; and sharing an amount so the parts add up.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 1
  entry          amount     running total
  coffee           3.10              3.10
  bus              2.70              5.80
  lunch           12.60             18.40
  refund          -5.00             13.40
  books           39.99             53.39
  snack            1.10             54.49
  tip              0.70             55.19
  milk             1.20             56.39
In whole cents: 56.39. Added as floats: 56.39000000000001.
They differ by 7.105427357601002e-15 - a report with two decimals prints that difference as 0.00.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 3
amount> 0.01
  times   as floats                exact          equal
     10   0.09999999999999999            0.10  no, low
    100   1.0000000000000007             1.00  no, high
   1000   9.999999999999831             10.00  no, low
   2000   20.000000000000327            20.00  no, high
Ten additions can already be off, and the float total can come out low for one count and high for the next.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 3
amount> 0.10
  times   as floats                exact          equal
     10   0.9999999999999999             1.00  no, low
    100   9.99999999999998              10.00  no, low
   1000   99.9999999999986             100.00  no, low
   2000   199.99999999999292           200.00  no, low
Ten additions can already be off, and the float total can come out low for one count and high for the next.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 3
amount> 19.99
  times   as floats                exact          equal
     10   199.9                        199.90  yes
    100   1999.0000000000007         1,999.00  no, high
   1000   19990.000000000135        19,990.00  no, high
   2000   39980.00000000042         39,980.00  no, high
Ten additions can already be off, and the float total can come out low for one count and high for the next.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 4
tax rate in percent (0.01 to 100)> 8.25
8.25% on every line: the exact tax is 4.652175; 0 of the 8 lines land exactly on half a cent.
  rule        lines rounded, then added   off by       the total rounded
  half-up             4.66             +0.007825          4.65
  half-even           4.66             +0.007825          4.65
  half-down           4.66             +0.007825          4.65
  truncate            4.61             -0.042175          4.65
half-up sends every half a cent away from zero, half-down toward it, and truncate drops the fraction. half-even goes to the even cent, so over many lines its halves tend to cancel out.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 6
empty, or halves (21 lines of 0.01, 0.03 ... 0.41)> halves
21 lines of odd cents: at 50% each one's tax is exactly half a cent over a whole one.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 4
tax rate in percent (0.01 to 100)> 50
50.00% on every line: the exact tax is 2.205000; 21 of the 21 lines land exactly on half a cent.
  rule        lines rounded, then added   off by       the total rounded
  half-up             2.31             +0.105000          2.21
  half-even           2.20             -0.005000          2.20
  half-down           2.10             -0.105000          2.20
  truncate            2.10             -0.105000          2.20
half-up sends every half a cent away from zero, half-down toward it, and truncate drops the fraction. half-even goes to the even cent, so over many lines its halves tend to cancel out.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 5
amount to share> 100
weights, whole numbers such as 1 1 1 or 50 30 20> 1 1 1
Sharing 100.00 by 1 1 1:
  each rounded      33.33  33.33  33.33  adds up to 99.99, not 100.00
  largest remainder 33.34  33.33  33.33
  running total     33.33  33.34  33.33
Rounding every part on its own can lose or invent cents. The largest remainders, or the rounded running total, always add up; they can give a leftover cent to different people.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 5
amount to share> 0.05
weights, whole numbers such as 1 1 1 or 50 30 20> 1 1 1 1
Sharing 0.05 by 1 1 1 1:
  each rounded      0.01  0.01  0.01  0.01  adds up to 0.04, not 0.05
  largest remainder 0.02  0.01  0.01  0.01
  running total     0.01  0.02  0.01  0.01
Rounding every part on its own can lose or invent cents. The largest remainders, or the rounded running total, always add up; they can give a leftover cent to different people.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 5
amount to share> 10
weights, whole numbers such as 1 1 1 or 50 30 20> 1 1 1 1 1 1 1
Sharing 10.00 by 1 1 1 1 1 1 1:
  each rounded      1.43  1.43  1.43  1.43  1.43  1.43  1.43  adds up to 10.01, not 10.00
  largest remainder 1.43  1.43  1.43  1.43  1.43  1.43  1.42
  running total     1.43  1.43  1.43  1.42  1.43  1.43  1.43
Rounding every part on its own can lose or invent cents. The largest remainders, or the rounded running total, always add up; they can give a leftover cent to different people.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 6
empty, or halves (21 lines of 0.01, 0.03 ... 0.41)> empty
The ledger is empty.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 2
what (up to 12 letters)> rent
amount> 850
rent 850.00 added.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 2
what (up to 12 letters)> deposit
amount> -850
deposit -850.00 added.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 1
  entry          amount     running total
  rent           850.00            850.00
  deposit       -850.00              0.00
In whole cents: 0.00. Added as floats: 0.0.
The two agree this time.

1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit
choice> 7
Bye.
What was typed (32 lines)
1
3
0.01
3
0.10
3
19.99
4
8.25
6
halves
4
50
5
100
1 1 1
5
0.05
1 1 1 1
5
10
1 1 1 1 1 1 1
6
empty
2
rent
850
2
deposit
-850
1
7

Modules

The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.

main.eml(entry)

eml
# P104 money ledger: a ledger kept in whole cents beside the same sums in
# floats, one amount added many times, tax on every line under four
# rounding rules, and an amount shared by weights.
import money

def plain(s):
    0 => i
    len(s) => j
    while i < j and s[i] == " ":
        i + 1 => i
    while j > i and s[j - 1] == " ":
        j - 1 => j
    return s[i:j]

def number(s, most):
    if len(s) == 0 or len(s) > most:
        return -1
    0 => n
    for c in s:
        if not (c >= "0" and c <= "9"):
            return -1
        n * 10 + int(c) => n
    return n

def pad(s, n):
    while len(s) < n:
        s + " " => s
    return s

def lpad(s, n):
    while len(s) < n:
        " " + s => s
    return s

def ask_amount(prompt):
    # [cents], [] to cancel, or ["!"] after a complaint
    plain(input(prompt + "> ")) => t
    if t == "":
        return []
    money.parse_amount(t) => a
    if len(a) == 0:
        "Type an amount such as 12.34 or -5, at most two decimals and 9,999,999.99." ^0
        return ["!"]
    return a

def show_ledger(entries):
    if len(entries) == 0:
        "The ledger is empty." ^0
        return
    "  entry          amount     running total" ^0
    0 => run
    for e in entries:
        run + e[1] => run
        ("  " + pad(e[0], 12) + lpad(money.cents_text(e[1]), 9) + lpad(money.cents_text(run), 18)) ^0
    money.float_total(entries) => f
    money.exact_total(entries) => x
    f - x / 100.0 => d
    ("In whole cents: " + money.cents_text(x) + ". Added as floats: " + str(f) + ".") ^0
    if d == 0.0:
        "The two agree this time." ^0
    else:
        ("They differ by " + str(d) + " - a report with two decimals prints that difference as " + money.float_cents_text(d) + ".") ^0

def many():
    ask_amount("amount") => a
    if len(a) == 0:
        "Cancelled." ^0
        return
    if a[0] == "!":
        return
    if a[0] == 0:
        "Pick an amount that is not zero." ^0
        return
    ("  times   as floats                exact          equal") ^0
    for n in [10, 100, 1000, 2000]:
        money.repeat(a[0], n) => r
        "yes" => eq
        if r[0] != r[1] / 100.0:
            "no, low" => eq
            if r[0] > r[1] / 100.0:
                "no, high" => eq
        ("  " + lpad(str(n), 5) + "   " + pad(str(r[0]), 22) + lpad(money.cents_text(r[1]), 13) + "  " + eq) ^0
    "Ten additions can already be off, and the float total can come out low for one count and high for the next." ^0

def micro(num):
    # num / 10000 cents, written as an amount with six decimals
    "" => sign
    num => a
    if num < 0:
        "-" => sign
        0 - num => a
    a % 10000 => f
    int((a - f) / 10000) => c
    str(f) => fs
    while len(fs) < 4:
        "0" + fs => fs
    money.cents_text(c) => ct
    return sign + ct + fs

def tax(entries):
    if len(entries) == 0:
        "The ledger is empty." ^0
        return
    plain(input("tax rate in percent (0.01 to 100)> ")) => t
    if t == "":
        "Cancelled." ^0
        return
    money.parse_amount(t) => r
    if len(r) == 0 or r[0] < 1 or r[0] > 10000:
        "Type a rate from 0.01 to 100, at most two decimals." ^0
        return
    r[0] => bp
    0 => ties
    for e in entries:
        if money.is_tie(e[1] * bp, 10000):
            ties + 1 => ties
    money.exact_total(entries) * bp => exact
    " lines land" => land
    if ties == 1:
        " line lands" => land
    (money.cents_text(bp) + "% on every line: the exact tax is " + micro(exact) + "; " + str(ties) + " of the " + str(len(entries)) + land + " exactly on half a cent.") ^0
    "  rule        lines rounded, then added   off by       the total rounded" ^0
    for rule in ["half-up", "half-even", "half-down", "truncate"]:
        0 => s
        for e in entries:
            s + money.round_div(e[1] * bp, 10000, rule) => s
        money.round_div(exact, 10000, rule) => once
        micro(s * 10000 - exact) => off
        if s * 10000 - exact > 0:
            "+" + off => off
        ("  " + pad(rule, 12) + lpad(money.cents_text(s), 12) + "             " + pad(off, 13) + lpad(money.cents_text(once), 10)) ^0
    "half-up sends every half a cent away from zero, half-down toward it, and truncate drops the fraction. half-even goes to the even cent, so over many lines its halves tend to cancel out." ^0

def share_menu():
    ask_amount("amount to share") => a
    if len(a) == 0:
        "Cancelled." ^0
        return
    if a[0] == "!":
        return
    if a[0] <= 0:
        "Share an amount above zero." ^0
        return
    plain(input("weights, whole numbers such as 1 1 1 or 50 30 20> ")) => t
    if t == "":
        "Cancelled." ^0
        return
    [] => ws
    "" => w
    True => ok
    for c in t + " ":
        if c == " ":
            if w != "":
                number(w, 4) => v
                if v < 1:
                    False => ok
                ws + [v] => ws
            "" => w
        else:
            w + c => w
    if not ok or len(ws) < 2 or len(ws) > 8:
        "Type 2 to 8 whole numbers from 1 to 9999." ^0
        return
    ("Sharing " + money.cents_text(a[0]) + " by " + t + ":") ^0
    ["each", "largest", "running"] => methods
    ["each rounded", "largest remainder", "running total"] => names
    for k in [0:2]:
        money.share(a[0], ws, methods[k]) => parts
        "" => line
        0 => sum
        for p in parts:
            if line != "":
                line + "  " => line
            line + money.cents_text(p) => line
            sum + p => sum
        "" => note
        if sum != a[0]:
            "  adds up to " + money.cents_text(sum) + ", not " + money.cents_text(a[0]) => note
        ("  " + pad(names[k], 18) + line + note) ^0
    "Rounding every part on its own can lose or invent cents. The largest remainders, or the rounded running total, always add up; they can give a leftover cent to different people." ^0

def add(entries):
    plain(input("what (up to 12 letters)> ")) => d
    if d == "":
        "Cancelled." ^0
        return entries
    if len(d) > 12:
        "Keep it to 12 characters." ^0
        return entries
    ask_amount("amount") => a
    if len(a) == 0:
        "Cancelled." ^0
        return entries
    if a[0] == "!":
        return entries
    if len(entries) >= 30:
        "The ledger holds 30 entries." ^0
        return entries
    (d + " " + money.cents_text(a[0]) + " added.") ^0
    return entries + [[d, a[0]]]

[["coffee", 310], ["bus", 270], ["lunch", 1260], ["refund", 0 - 500], ["books", 3999], ["snack", 110], ["tip", 70], ["milk", 120]] => entries

"== Money ledger ==" ^0
"Money kept in whole cents, next to the same sums added as floats; rounding rules for fractions of a cent; and sharing an amount so the parts add up." ^0
True => running
while running:
    "" ^0
    "1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit" ^0
    plain(input("choice> ")) => choice
    if choice == "1":
        show_ledger(entries)
    elif choice == "2":
        add(entries) => entries
    elif choice == "3":
        many()
    elif choice == "4":
        tax(entries)
    elif choice == "5":
        share_menu()
    elif choice == "6":
        plain(input("empty, or halves (21 lines of 0.01, 0.03 ... 0.41)> ")) => k
        if k == "empty":
            [] => entries
            "The ledger is empty." ^0
        elif k == "halves":
            [] => entries
            for i in [0:20]:
                entries + [["line " + str(i + 1), 2 * i + 1]] => entries
            "21 lines of odd cents: at 50% each one's tax is exactly half a cent over a whole one." ^0
        elif k == "":
            "Cancelled." ^0
        else:
            "Type empty or halves." ^0
    elif choice == "7":
        False => running
    else:
        "Pick a number from 1 to 7." ^0
"Bye." ^0
Python projection (main.py)
import money

def plain(s):
    i = 0
    j = len(s)
    while i < j and s[i] == " ":
        i = i + 1
    while j > i and s[j - 1] == " ":
        j = j - 1
    return s[i:j]

def number(s, most):
    if len(s) == 0 or len(s) > most:
        return -1
    n = 0
    for c in s:
        if not (c >= "0" and c <= "9"):
            return -1
        n = n * 10 + int(c)
    return n

def pad(s, n):
    while len(s) < n:
        s = s + " "
    return s

def lpad(s, n):
    while len(s) < n:
        s = " " + s
    return s

def ask_amount(prompt):
    t = plain(input(prompt + "> "))
    if t == "":
        return []
    a = money.parse_amount(t)
    if len(a) == 0:
        print("Type an amount such as 12.34 or -5, at most two decimals and 9,999,999.99.")
        return ["!"]
    return a

def show_ledger(entries):
    if len(entries) == 0:
        print("The ledger is empty.")
        return
    print("  entry          amount     running total")
    run = 0
    for e in entries:
        run = run + e[1]
        print("  " + pad(e[0], 12) + lpad(money.cents_text(e[1]), 9) + lpad(money.cents_text(run), 18))
    f = money.float_total(entries)
    x = money.exact_total(entries)
    d = f - x / 100.0
    print("In whole cents: " + money.cents_text(x) + ". Added as floats: " + str(f) + ".")
    if d == 0.0:
        print("The two agree this time.")
    else:
        print("They differ by " + str(d) + " - a report with two decimals prints that difference as " + money.float_cents_text(d) + ".")

def many():
    a = ask_amount("amount")
    if len(a) == 0:
        print("Cancelled.")
        return
    if a[0] == "!":
        return
    if a[0] == 0:
        print("Pick an amount that is not zero.")
        return
    print("  times   as floats                exact          equal")
    for n in [10, 100, 1000, 2000]:
        r = money.repeat(a[0], n)
        eq = "yes"
        if r[0] != r[1] / 100.0:
            eq = "no, low"
            if r[0] > r[1] / 100.0:
                eq = "no, high"
        print("  " + lpad(str(n), 5) + "   " + pad(str(r[0]), 22) + lpad(money.cents_text(r[1]), 13) + "  " + eq)
    print("Ten additions can already be off, and the float total can come out low for one count and high for the next.")

def micro(num):
    sign = ""
    a = num
    if num < 0:
        sign = "-"
        a = 0 - num
    f = a % 10000
    c = int((a - f) / 10000)
    fs = str(f)
    while len(fs) < 4:
        fs = "0" + fs
    ct = money.cents_text(c)
    return sign + ct + fs

def tax(entries):
    if len(entries) == 0:
        print("The ledger is empty.")
        return
    t = plain(input("tax rate in percent (0.01 to 100)> "))
    if t == "":
        print("Cancelled.")
        return
    r = money.parse_amount(t)
    if len(r) == 0 or r[0] < 1 or r[0] > 10000:
        print("Type a rate from 0.01 to 100, at most two decimals.")
        return
    bp = r[0]
    ties = 0
    for e in entries:
        if money.is_tie(e[1] * bp, 10000):
            ties = ties + 1
    exact = money.exact_total(entries) * bp
    land = " lines land"
    if ties == 1:
        land = " line lands"
    print(money.cents_text(bp) + "% on every line: the exact tax is " + micro(exact) + "; " + str(ties) + " of the " + str(len(entries)) + land + " exactly on half a cent.")
    print("  rule        lines rounded, then added   off by       the total rounded")
    for rule in ["half-up", "half-even", "half-down", "truncate"]:
        s = 0
        for e in entries:
            s = s + money.round_div(e[1] * bp, 10000, rule)
        once = money.round_div(exact, 10000, rule)
        off = micro(s * 10000 - exact)
        if s * 10000 - exact > 0:
            off = "+" + off
        print("  " + pad(rule, 12) + lpad(money.cents_text(s), 12) + "             " + pad(off, 13) + lpad(money.cents_text(once), 10))
    print("half-up sends every half a cent away from zero, half-down toward it, and truncate drops the fraction. half-even goes to the even cent, so over many lines its halves tend to cancel out.")

def share_menu():
    a = ask_amount("amount to share")
    if len(a) == 0:
        print("Cancelled.")
        return
    if a[0] == "!":
        return
    if a[0] <= 0:
        print("Share an amount above zero.")
        return
    t = plain(input("weights, whole numbers such as 1 1 1 or 50 30 20> "))
    if t == "":
        print("Cancelled.")
        return
    ws = []
    w = ""
    ok = True
    for c in t + " ":
        if c == " ":
            if w != "":
                v = number(w, 4)
                if v < 1:
                    ok = False
                ws = ws + [v]
            w = ""
        else:
            w = w + c
    if not ok or len(ws) < 2 or len(ws) > 8:
        print("Type 2 to 8 whole numbers from 1 to 9999.")
        return
    print("Sharing " + money.cents_text(a[0]) + " by " + t + ":")
    methods = ["each", "largest", "running"]
    names = ["each rounded", "largest remainder", "running total"]
    for k in range(0, 3):
        parts = money.share(a[0], ws, methods[k])
        line = ""
        sum = 0
        for p in parts:
            if line != "":
                line = line + "  "
            line = line + money.cents_text(p)
            sum = sum + p
        note = ""
        if sum != a[0]:
            note = "  adds up to " + money.cents_text(sum) + ", not " + money.cents_text(a[0])
        print("  " + pad(names[k], 18) + line + note)
    print("Rounding every part on its own can lose or invent cents. The largest remainders, or the rounded running total, always add up; they can give a leftover cent to different people.")

def add(entries):
    d = plain(input("what (up to 12 letters)> "))
    if d == "":
        print("Cancelled.")
        return entries
    if len(d) > 12:
        print("Keep it to 12 characters.")
        return entries
    a = ask_amount("amount")
    if len(a) == 0:
        print("Cancelled.")
        return entries
    if a[0] == "!":
        return entries
    if len(entries) >= 30:
        print("The ledger holds 30 entries.")
        return entries
    print(d + " " + money.cents_text(a[0]) + " added.")
    return entries + [[d, a[0]]]

entries = [["coffee", 310], ["bus", 270], ["lunch", 1260], ["refund", 0 - 500], ["books", 3999], ["snack", 110], ["tip", 70], ["milk", 120]]
print("== Money ledger ==")
print("Money kept in whole cents, next to the same sums added as floats; rounding rules for fractions of a cent; and sharing an amount so the parts add up.")
running = True
while running:
    print("")
    print("1) ledger  2) add an entry  3) one amount many times  4) tax on every line  5) share an amount  6) new ledger  7) quit")
    choice = plain(input("choice> "))
    if choice == "1":
        show_ledger(entries)
    elif choice == "2":
        entries = add(entries)
    elif choice == "3":
        many()
    elif choice == "4":
        tax(entries)
    elif choice == "5":
        share_menu()
    elif choice == "6":
        k = plain(input("empty, or halves (21 lines of 0.01, 0.03 ... 0.41)> "))
        if k == "empty":
            entries = []
            print("The ledger is empty.")
        elif k == "halves":
            entries = []
            for i in range(0, 21):
                entries = entries + [["line " + str(i + 1), 2 * i + 1]]
            print("21 lines of odd cents: at 50% each one's tax is exactly half a cent over a whole one.")
        elif k == "":
            print("Cancelled.")
        else:
            print("Type empty or halves.")
    elif choice == "7":
        running = False
    else:
        print("Pick a number from 1 to 7.")
print("Bye.")

money.eml

eml
# P104 money ledger - money in whole cents, beside the same sums in floats
# (the corpus case money-float-accumulation: ten items are already off,
# and the side the error falls on is not stable); fractions of a cent
# rounded by four rules, whose drift over many ties is the corpus case
# rounding-rule-drift; and an amount shared by weights so that the parts
# add up to it exactly.

def cents_text(c):
    # 123456 -> "1,234.56", -5 -> "-0.05"
    "" => sign
    c => a
    if c < 0:
        "-" => sign
        0 - c => a
    a % 100 => small
    int((a - small) / 100) => big
    str(big) => s
    "" => out
    0 => k
    for i in [0:len(s) - 1]:
        s[len(s) - 1 - i] + out => out
        k + 1 => k
        if k % 3 == 0 and i < len(s) - 1:
            "," + out => out
    if small < 10:
        return sign + out + ".0" + str(small)
    return sign + out + "." + str(small)

def parse_amount(t):
    # "12.34", "-5", "0.5" -> cents, or [] if not a sum of money with at
    # most two decimals and at most 9,999,999.99
    if len(t) == 0:
        return []
    1 => sign
    t => s
    if s[0] == "-":
        0 - 1 => sign
        s[1:len(s)] => s
    "" => whole
    "" => frac
    False => dot
    for c in s:
        if c == ".":
            if dot:
                return []
            True => dot
        elif c >= "0" and c <= "9":
            if dot:
                frac + c => frac
            else:
                whole + c => whole
        else:
            return []
    if whole == "" or len(frac) > 2 or (dot and frac == "") or len(whole) > 7:
        return []
    while len(frac) < 2:
        frac + "0" => frac
    return [sign * (int(whole) * 100 + int(frac))]

def float_total(entries):
    0.0 => t
    for e in entries:
        t + e[1] / 100.0 => t
    return t

def exact_total(entries):
    0 => t
    for e in entries:
        t + e[1] => t
    return t

def float_cents_text(x):
    # how a report with two decimals shows a float: the cents nearest it,
    # and "-0.00" for a tiny negative difference, as Python's %.2f does
    x * 100.0 => y
    "" => sign
    if y < 0:
        "-" => sign
        0.0 - y => y
    int(y + 0.5) => c
    if sign == "-" and c == 0:
        return "-0.00"
    if sign == "-":
        return cents_text(0 - c)
    return cents_text(c)

def repeat(amount, n):
    # [float total, exact cents] of n times the same amount
    0.0 => t
    for i in [1:n]:
        t + amount / 100.0 => t
    return [t, amount * n]

def round_div(num, den, rule):
    # num / den (den > 0) to a whole number by rule: "half-up" (half away
    # from zero), "half-even", "half-down" (half toward zero), "truncate"
    0 => neg
    num => a
    if num < 0:
        1 => neg
        0 - num => a
    a % den => r
    int((a - r) / den) => q
    if rule == "truncate":
        0 => up
    elif 2 * r > den:
        1 => up
    elif 2 * r < den:
        0 => up
    elif rule == "half-up":
        1 => up
    elif rule == "half-down":
        0 => up
    else:
        q % 2 => up
    q + up => q
    if neg == 1:
        return 0 - q
    return q

def is_tie(num, den):
    0 => a
    num => a
    if num < 0:
        0 - num => a
    return 2 * (a % den) == den

def share(total, weights, method):
    # parts that add up to total, by weight; "each" rounds every part on
    # its own (half-up) and may not add up; "largest" gives the cents left
    # over to the largest remainders; "running" rounds the running sum
    0 => w
    for x in weights:
        w + x => w
    [] => out
    if method == "each":
        for x in weights:
            out + [round_div(total * x, w, "half-up")] => out
        return out
    if method == "running":
        0 => run
        0 => before
        for x in weights:
            run + x => run
            round_div(total * run, w, "half-up") => now
            out + [now - before] => out
            now => before
        return out
    # largest remainder: floors first, then one cent each to the largest
    # remainders, earlier first on ties
    [] => rems
    0 => given
    for x in weights:
        total * x => num
        num % w => r
        int((num - r) / w) => f
        out + [f] => out
        rems + [r] => rems
        given + f => given
    total - given => left
    [] => used
    for x in weights:
        used + [False] => used
    0 => k
    while k < left:
        -1 => best
        for i in [0:len(weights) - 1]:
            if not used[i] and (best == -1 or rems[i] > rems[best]):
                i => best
        True => used[best]
        out[best] + 1 => out[best]
        k + 1 => k
    return out
Python projection (money.py)
def cents_text(c):
    sign = ""
    a = c
    if c < 0:
        sign = "-"
        a = 0 - c
    small = a % 100
    big = int((a - small) / 100)
    s = str(big)
    out = ""
    k = 0
    for i in range(0, len(s)):
        out = s[len(s) - 1 - i] + out
        k = k + 1
        if k % 3 == 0 and i < len(s) - 1:
            out = "," + out
    if small < 10:
        return sign + out + ".0" + str(small)
    return sign + out + "." + str(small)

def parse_amount(t):
    if len(t) == 0:
        return []
    sign = 1
    s = t
    if s[0] == "-":
        sign = 0 - 1
        s = s[1:len(s)]
    whole = ""
    frac = ""
    dot = False
    for c in s:
        if c == ".":
            if dot:
                return []
            dot = True
        elif c >= "0" and c <= "9":
            if dot:
                frac = frac + c
            else:
                whole = whole + c
        else:
            return []
    if whole == "" or len(frac) > 2 or dot and frac == "" or len(whole) > 7:
        return []
    while len(frac) < 2:
        frac = frac + "0"
    return [sign * (int(whole) * 100 + int(frac))]

def float_total(entries):
    t = 0.0
    for e in entries:
        t = t + e[1] / 100.0
    return t

def exact_total(entries):
    t = 0
    for e in entries:
        t = t + e[1]
    return t

def float_cents_text(x):
    y = x * 100.0
    sign = ""
    if y < 0:
        sign = "-"
        y = 0.0 - y
    c = int(y + 0.5)
    if sign == "-" and c == 0:
        return "-0.00"
    if sign == "-":
        return cents_text(0 - c)
    return cents_text(c)

def repeat(amount, n):
    t = 0.0
    for i in range(1, n+1):
        t = t + amount / 100.0
    return [t, amount * n]

def round_div(num, den, rule):
    neg = 0
    a = num
    if num < 0:
        neg = 1
        a = 0 - num
    r = a % den
    q = int((a - r) / den)
    if rule == "truncate":
        up = 0
    elif 2 * r > den:
        up = 1
    elif 2 * r < den:
        up = 0
    elif rule == "half-up":
        up = 1
    elif rule == "half-down":
        up = 0
    else:
        up = q % 2
    q = q + up
    if neg == 1:
        return 0 - q
    return q

def is_tie(num, den):
    a = 0
    a = num
    if num < 0:
        a = 0 - num
    return 2 * (a % den) == den

def share(total, weights, method):
    w = 0
    for x in weights:
        w = w + x
    out = []
    if method == "each":
        for x in weights:
            out = out + [round_div(total * x, w, "half-up")]
        return out
    if method == "running":
        run = 0
        before = 0
        for x in weights:
            run = run + x
            now = round_div(total * run, w, "half-up")
            out = out + [now - before]
            before = now
        return out
    rems = []
    given = 0
    for x in weights:
        num = total * x
        r = num % w
        f = int((num - r) / w)
        out = out + [f]
        rems = rems + [r]
        given = given + f
    left = total - given
    used = []
    for x in weights:
        used = used + [False]
    k = 0
    while k < left:
        best = -1
        for i in range(0, len(weights)):
            if not used[i] and (best == -1 or rems[i] > rems[best]):
                best = i
        used[best] = True
        out[best] = out[best] + 1
        k = k + 1
    return out

Built on these corpus cases

ΣEML 2026

Efficient New Language — a high-density semantic overlay for humans and AI agents.

This site’s demos run on the real EML toolchain, in your browser.

© 2026 EveMissLab (一言諾科技有限公司) / Neo.K. All rights reserved.