Travel expenses
A trip paid by card in four currencies and reimbursed in dollars: an amount column that adds yen to cents, lines summed at ten days' rates, and a card that quotes at authorization and charges at capture.
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 ten-day trip paid by card in four currencies and reimbursed in dollars: what each total is a sum of.
main.eml- the menu, the expenses, the rates, the totals, the card's quote and charge, and an expense of your ownfx.eml- the currencies and their minor units, the rates day by day, the expenses, and conversion to dollar cents
How each part works:
- Seven expenses in dollars, yen, pounds and euros, each authorized on one day and captured on a later one; the rates move both ways over the ten days. Amounts are whole minor units (yen has none smaller), rates are in millionths of a dollar, and a conversion is rounded half-up to the cent.
- As in the corpus case
the-total-was-checked-and-the-currencies-were-mixed, the amount column adds up to 301,490: cents, yen, pence and euro cents in one number, which a check of the lines against a stored sum of the same column passes. By currency the trip is 842.00 USD, 108,800 JPY, 578.50 GBP and 506.40 EUR. - As in
the-amounts-were-summed-at-each-days-rate, each line at the rate of the day it was paid comes to 2,848.50, a sum over the rates of six days; every line at the last day's rate, the value at one moment, is 2,864.65. - As in
the-exchange-rate-was-right-and-its-timestamp-was-not, the card quotes at authorization and charges at capture: 2,848.50 quoted and 2,852.27 charged. Five lines were charged more than their quote and the Tokyo hotel 0.96 less, the side nobody writes in about.
What is checked: menu choices 1 to 6; a currency (USD, JPY, GBP or EUR, in capitals or not); an amount in yen from 1 to 9999999, or otherwise written as 12, 12.5 or 12.50 from 0.01 to 99999.99; a day authorized from 1 to 10 and a day captured from that day to 10. An empty answer cancels.
Sessions: sessions/basic.in gives: - the expenses, the rates, the totals, and the quote and charge; - 85.50 EUR from day 3 to day 4 (charged more), 64,000 JPY from day 2 to day 5 (charged less), and 10.00 USD (the charge is the quote).
sessions/bad-input.in gives: - menu choices 0 and x; - currencies: a cancel and CHF; - yen: a cancel, 0, 10000000 and 1.5; pounds: a cancel, 0, 100000, 12.345 and x; - days authorized: a cancel, 0 and 11; days captured: a cancel, 3, 11 and x; - 20.00 GBP authorized and captured on day 4.
Built on the verified corpus cases the-total-was-checked-and-the-currencies-were-mixed (a nightly reconciliation that adds the amount column across nine currencies), the-amounts-were-summed-at-each-days-rate (a balance whose lines keep the rates of their own days) and the-exchange-rate-was-right-and-its-timestamp-was-not (a payment quoted at authorization and settled at capture).
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== Travel expenses ==
A trip paid in four currencies and reimbursed in dollars.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 0
Pick a number from 1 to 6.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> x
Pick a number from 1 to 6.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 5
currency (USD, JPY, GBP or EUR)>
Cancelled.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 5
currency (USD, JPY, GBP or EUR)> chf
Type USD, JPY, GBP or EUR.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 5
currency (USD, JPY, GBP or EUR)> jpy
amount in yen (1 to 9999999)>
Cancelled.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 5
currency (USD, JPY, GBP or EUR)> jpy
amount in yen (1 to 9999999)> 0
Type a whole number of yen from 1 to 9999999.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 5
currency (USD, JPY, GBP or EUR)> jpy
amount in yen (1 to 9999999)> 10000000
Type a whole number of yen from 1 to 9999999.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 5
currency (USD, JPY, GBP or EUR)> jpy
amount in yen (1 to 9999999)> 1.5
Type a whole number of yen from 1 to 9999999.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 5
currency (USD, JPY, GBP or EUR)> gbp
amount (0.01 to 99999.99)>
Cancelled.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 5
currency (USD, JPY, GBP or EUR)> gbp
amount (0.01 to 99999.99)> 0
Type an amount such as 12.50, from 0.01 to 99999.99.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 5
currency (USD, JPY, GBP or EUR)> gbp
amount (0.01 to 99999.99)> 100000
Type an amount such as 12.50, from 0.01 to 99999.99.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 5
currency (USD, JPY, GBP or EUR)> gbp
amount (0.01 to 99999.99)> 12.345
Type an amount such as 12.50, from 0.01 to 99999.99.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 5
currency (USD, JPY, GBP or EUR)> gbp
amount (0.01 to 99999.99)> x
Type an amount such as 12.50, from 0.01 to 99999.99.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 5
currency (USD, JPY, GBP or EUR)> gbp
amount (0.01 to 99999.99)> 20
day authorized (1 to 10)>
Cancelled.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 5
currency (USD, JPY, GBP or EUR)> gbp
amount (0.01 to 99999.99)> 20
day authorized (1 to 10)> 0
Type a whole number from 1 to 10.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 5
currency (USD, JPY, GBP or EUR)> gbp
amount (0.01 to 99999.99)> 20
day authorized (1 to 10)> 11
Type a whole number from 1 to 10.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 5
currency (USD, JPY, GBP or EUR)> gbp
amount (0.01 to 99999.99)> 20
day authorized (1 to 10)> 4
day captured (4 to 10)>
Cancelled.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 5
currency (USD, JPY, GBP or EUR)> gbp
amount (0.01 to 99999.99)> 20
day authorized (1 to 10)> 4
day captured (4 to 10)> 3
Type a whole number from 4 to 10.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 5
currency (USD, JPY, GBP or EUR)> gbp
amount (0.01 to 99999.99)> 20
day authorized (1 to 10)> 4
day captured (4 to 10)> 11
Type a whole number from 4 to 10.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 5
currency (USD, JPY, GBP or EUR)> gbp
amount (0.01 to 99999.99)> 20
day authorized (1 to 10)> 4
day captured (4 to 10)> x
Type a whole number from 4 to 10.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 5
currency (USD, JPY, GBP or EUR)> GBP
amount (0.01 to 99999.99)> 20
day authorized (1 to 10)> 4
day captured (4 to 10)> 4
Quoted at authorization, day 4: 25.32; charged at capture, day 4: 25.32.
The charge is the quote.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 6
Bye.
What was typed (71 lines)
0
x
5
5
chf
5
jpy
5
jpy
0
5
jpy
10000000
5
jpy
1.5
5
gbp
5
gbp
0
5
gbp
100000
5
gbp
12.345
5
gbp
x
5
gbp
20
5
gbp
20
0
5
gbp
20
11
5
gbp
20
4
5
gbp
20
4
3
5
gbp
20
4
11
5
gbp
20
4
x
5
GBP
20
4
4
6
basic
interpreter: byte-equal== Travel expenses ==
A trip paid in four currencies and reimbursed in dollars.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 1
The trip, ten days, every payment by card, reimbursed in dollars:
item amount authorized captured
flight 842.00 USD day 1 day 1
hotel, Tokyo, 3 nights 96,000 JPY day 2 day 5
dinner, Tokyo 12,800 JPY day 3 day 4
hotel, London, 3 nights 540.00 GBP day 5 day 9
taxi, London 38.50 GBP day 6 day 7
hotel, Paris, 2 nights 410.00 EUR day 8 day 10
dinner, Paris 96.40 EUR day 9 day 10
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 2
Dollars for one unit, day by day:
day JPY GBP EUR
1 0.006600 1.2600 1.0800
2 0.006620 1.2640 1.0825
3 0.006585 1.2615 1.0790
4 0.006640 1.2660 1.0840
5 0.006610 1.2705 1.0870
6 0.006655 1.2680 1.0835
7 0.006670 1.2720 1.0880
8 0.006650 1.2695 1.0905
9 0.006690 1.2750 1.0860
10 0.006705 1.2790 1.0925
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 3
The amount column added: 301,490 - cents, yen, pence and euro cents in one number.
The report stores the same sum as its total, so the check of the lines against the total passes, and the total is in no currency.
By currency: 842.00 USD, 108,800 JPY, 578.50 GBP, 506.40 EUR.
In dollars, each line at the rate of the day it was paid: 2,848.50.
Every line at the last day's rate: 2,864.65, which is 16.15 more: the value at one moment, where the first total mixes the rates of 6 days.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 4
Quoted at authorization and charged at capture, in dollars:
item quoted charged difference
flight 842.00 842.00 0.00
hotel, Tokyo, 3 nights 635.52 634.56 -0.96
dinner, Tokyo 84.29 84.99 +0.70
hotel, London, 3 nights 686.07 688.50 +2.43
taxi, London 48.82 48.97 +0.15
hotel, Paris, 2 nights 447.11 447.93 +0.82
dinner, Paris 104.69 105.32 +0.63
Quoted in all 2,848.50, charged 2,852.27: +3.77.
5 lines were charged more than their quote and 1 less. Only those charged more get written in about, so the complaints see one side of a difference that runs both ways.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 5
currency (USD, JPY, GBP or EUR)> eur
amount (0.01 to 99999.99)> 85.50
day authorized (1 to 10)> 3
day captured (3 to 10)> 4
Quoted at authorization, day 3: 92.25; charged at capture, day 4: 92.68.
+0.43: charged more than the quote the traveller agreed to.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 5
currency (USD, JPY, GBP or EUR)> JPY
amount in yen (1 to 9999999)> 64000
day authorized (1 to 10)> 2
day captured (2 to 10)> 5
Quoted at authorization, day 2: 423.68; charged at capture, day 5: 423.04.
-0.64: charged less than the quote, and nobody writes in about that.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 5
currency (USD, JPY, GBP or EUR)> usd
amount (0.01 to 99999.99)> 10
day authorized (1 to 10)> 1
day captured (1 to 10)> 9
Quoted at authorization, day 1: 10.00; charged at capture, day 9: 10.00.
The charge is the quote.
1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit
choice> 6
Bye.
What was typed (20 lines)
1
2
3
4
5
eur
85.50
3
4
5
JPY
64000
2
5
5
usd
10
1
9
6
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# P164 travel expenses: a trip paid in four currencies - the expenses, the
# rates, what the totals are sums of, the card's quote and charge, and an
# expense of your own.
import fx
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 pad(s, n):
s => out
while len(out) < n:
out + " " => out
return out
def lpad(s, n):
s => out
while len(out) < n:
" " + out => out
return out
def digits(t):
for c in t:
if c < "0" or c > "9":
return False
return len(t) > 0
def commas(n):
str(n) => s
"" => out
0 => k
len(s) => i
while i > 0:
if k == 3:
"," + out => out
0 => k
s[i - 1] + out => out
k + 1 => k
i - 1 => i
return out
def zeros(n, places):
str(n) => s
while len(s) < places:
"0" + s => s
return s
def money(c):
# c >= 0 cents
return commas(int((c - c % 100) / 100)) + "." + zeros(c % 100, 2)
def signed(c):
if c > 0:
return "+" + money(c)
if c < 0:
return "-" + money(0 - c)
return "0.00"
def amount(cur, m):
if fx.places(cur) == 0:
return commas(m) + " " + cur
return money(m) + " " + cur
def show_expenses():
"The trip, ten days, every payment by card, reimbursed in dollars:" ^0
(" " + pad("item", 25) + lpad("amount", 14) + " " + pad("authorized", 12) + "captured") ^0
for e in fx.expenses():
(" " + pad(e[0], 25) + lpad(amount(e[1], e[2]), 14) + " " + pad("day " + str(e[3]), 12) + "day " + str(e[4])) ^0
def show_rates():
"Dollars for one unit, day by day:" ^0
(" " + pad("day", 4) + lpad("JPY", 10) + lpad("GBP", 9) + lpad("EUR", 9)) ^0
1 => d
while d <= 10:
fx.rate("JPY", d) => j
fx.rate("GBP", d) => g
fx.rate("EUR", d) => u
(" " + pad(str(d), 4) + lpad("0." + zeros(j, 6), 10) + lpad("1." + zeros(int((g - 1000000) / 100), 4), 9) + lpad("1." + zeros(int((u - 1000000) / 100), 4), 9)) ^0
d + 1 => d
def show_totals():
0 => column
0 => own_day
0 => last_day
[] => days
for e in fx.expenses():
column + e[2] => column
own_day + fx.cents(e[1], e[2], e[3]) => own_day
last_day + fx.cents(e[1], e[2], 10) => last_day
False => found
for d in days:
if d == e[3]:
True => found
if not found and e[1] != "USD":
days + [e[3]] => days
("The amount column added: " + commas(column) + " - cents, yen, pence and euro cents in one number.") ^0
"The report stores the same sum as its total, so the check of the lines against the total passes, and the total is in no currency." ^0
"" => by
for c in fx.currencies():
0 => t
for e in fx.expenses():
if e[1] == c[0]:
t + e[2] => t
if by != "":
by + ", " => by
by + amount(c[0], t) => by
("By currency: " + by + ".") ^0
("In dollars, each line at the rate of the day it was paid: " + money(own_day) + ".") ^0
("Every line at the last day's rate: " + money(last_day) + ", which is " + money(last_day - own_day) + " more: the value at one moment, where the first total mixes the rates of " + str(len(days)) + " days.") ^0
def show_card():
"Quoted at authorization and charged at capture, in dollars:" ^0
(" " + pad("item", 25) + lpad("quoted", 9) + lpad("charged", 10) + lpad("difference", 12)) ^0
0 => q
0 => ch
0 => more
0 => less
for e in fx.expenses():
fx.cents(e[1], e[2], e[3]) => a
fx.cents(e[1], e[2], e[4]) => b
q + a => q
ch + b => ch
if b > a:
more + 1 => more
if b < a:
less + 1 => less
(" " + pad(e[0], 25) + lpad(money(a), 9) + lpad(money(b), 10) + lpad(signed(b - a), 12)) ^0
("Quoted in all " + money(q) + ", charged " + money(ch) + ": " + signed(ch - q) + ".") ^0
(str(more) + " lines were charged more than their quote and " + str(less) + " less. Only those charged more get written in about, so the complaints see one side of a difference that runs both ways.") ^0
def price(t, top):
# cents for an amount typed as 12, 12.5 or 12.50 with up to top digits
# before the point; -1 if it is not one
0 => dot
0 => i
0 => at
for c in t:
if c == ".":
dot + 1 => dot
i => at
elif c < "0" or c > "9":
return 0 - 1
i + 1 => i
if dot == 0:
if len(t) < 1 or len(t) > top:
return 0 - 1
return int(t) * 100
if dot > 1 or at < 1 or at > top or len(t) - at - 1 < 1 or len(t) - at - 1 > 2:
return 0 - 1
t[at + 1:] => f
if len(f) == 1:
f + "0" => f
return int(t[0:at]) * 100 + int(f)
def own():
plain(input("currency (USD, JPY, GBP or EUR)> ")) => t
if t == "":
"Cancelled." ^0
return
"" => cur
0 => k
for c in ["USD", "JPY", "GBP", "EUR"]:
if t == c or t == ["usd", "jpy", "gbp", "eur"][k]:
c => cur
k + 1 => k
if cur == "":
"Type USD, JPY, GBP or EUR." ^0
return
if cur == "JPY":
plain(input("amount in yen (1 to 9999999)> ")) => a
if a == "":
"Cancelled." ^0
return
if len(a) > 7 or not digits(a) or int(a) < 1:
"Type a whole number of yen from 1 to 9999999." ^0
return
int(a) => m
else:
plain(input("amount (0.01 to 99999.99)> ")) => a
if a == "":
"Cancelled." ^0
return
price(a, 5) => m
if m < 1:
"Type an amount such as 12.50, from 0.01 to 99999.99." ^0
return
plain(input("day authorized (1 to 10)> ")) => d1
if d1 == "":
"Cancelled." ^0
return
if len(d1) > 2 or not digits(d1) or int(d1) < 1 or int(d1) > 10:
"Type a whole number from 1 to 10." ^0
return
int(d1) => a1
plain(input("day captured (" + str(a1) + " to 10)> ")) => d2
if d2 == "":
"Cancelled." ^0
return
if len(d2) > 2 or not digits(d2) or int(d2) < a1 or int(d2) > 10:
("Type a whole number from " + str(a1) + " to 10.") ^0
return
int(d2) => a2
fx.cents(cur, m, a1) => qa
fx.cents(cur, m, a2) => cb
("Quoted at authorization, day " + str(a1) + ": " + money(qa) + "; charged at capture, day " + str(a2) + ": " + money(cb) + ".") ^0
if cb > qa:
(signed(cb - qa) + ": charged more than the quote the traveller agreed to.") ^0
elif cb < qa:
(signed(cb - qa) + ": charged less than the quote, and nobody writes in about that.") ^0
else:
"The charge is the quote." ^0
"== Travel expenses ==" ^0
"A trip paid in four currencies and reimbursed in dollars." ^0
True => running
while running:
"" ^0
"1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit" ^0
plain(input("choice> ")) => choice
if choice == "1":
show_expenses()
elif choice == "2":
show_rates()
elif choice == "3":
show_totals()
elif choice == "4":
show_card()
elif choice == "5":
own()
elif choice == "6":
False => running
else:
"Pick a number from 1 to 6." ^0
"Bye." ^0
Python projection (main.py)
import fx
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 pad(s, n):
out = s
while len(out) < n:
out = out + " "
return out
def lpad(s, n):
out = s
while len(out) < n:
out = " " + out
return out
def digits(t):
for c in t:
if c < "0" or c > "9":
return False
return len(t) > 0
def commas(n):
s = str(n)
out = ""
k = 0
i = len(s)
while i > 0:
if k == 3:
out = "," + out
k = 0
out = s[i - 1] + out
k = k + 1
i = i - 1
return out
def zeros(n, places):
s = str(n)
while len(s) < places:
s = "0" + s
return s
def money(c):
return commas(int((c - c % 100) / 100)) + "." + zeros(c % 100, 2)
def signed(c):
if c > 0:
return "+" + money(c)
if c < 0:
return "-" + money(0 - c)
return "0.00"
def amount(cur, m):
if fx.places(cur) == 0:
return commas(m) + " " + cur
return money(m) + " " + cur
def show_expenses():
print("The trip, ten days, every payment by card, reimbursed in dollars:")
print(" " + pad("item", 25) + lpad("amount", 14) + " " + pad("authorized", 12) + "captured")
for e in fx.expenses():
print(" " + pad(e[0], 25) + lpad(amount(e[1], e[2]), 14) + " " + pad("day " + str(e[3]), 12) + "day " + str(e[4]))
def show_rates():
print("Dollars for one unit, day by day:")
print(" " + pad("day", 4) + lpad("JPY", 10) + lpad("GBP", 9) + lpad("EUR", 9))
d = 1
while d <= 10:
j = fx.rate("JPY", d)
g = fx.rate("GBP", d)
u = fx.rate("EUR", d)
print(" " + pad(str(d), 4) + lpad("0." + zeros(j, 6), 10) + lpad("1." + zeros(int((g - 1000000) / 100), 4), 9) + lpad("1." + zeros(int((u - 1000000) / 100), 4), 9))
d = d + 1
def show_totals():
column = 0
own_day = 0
last_day = 0
days = []
for e in fx.expenses():
column = column + e[2]
own_day = own_day + fx.cents(e[1], e[2], e[3])
last_day = last_day + fx.cents(e[1], e[2], 10)
found = False
for d in days:
if d == e[3]:
found = True
if not found and e[1] != "USD":
days = days + [e[3]]
print("The amount column added: " + commas(column) + " - cents, yen, pence and euro cents in one number.")
print("The report stores the same sum as its total, so the check of the lines against the total passes, and the total is in no currency.")
by = ""
for c in fx.currencies():
t = 0
for e in fx.expenses():
if e[1] == c[0]:
t = t + e[2]
if by != "":
by = by + ", "
by = by + amount(c[0], t)
print("By currency: " + by + ".")
print("In dollars, each line at the rate of the day it was paid: " + money(own_day) + ".")
print("Every line at the last day's rate: " + money(last_day) + ", which is " + money(last_day - own_day) + " more: the value at one moment, where the first total mixes the rates of " + str(len(days)) + " days.")
def show_card():
print("Quoted at authorization and charged at capture, in dollars:")
print(" " + pad("item", 25) + lpad("quoted", 9) + lpad("charged", 10) + lpad("difference", 12))
q = 0
ch = 0
more = 0
less = 0
for e in fx.expenses():
a = fx.cents(e[1], e[2], e[3])
b = fx.cents(e[1], e[2], e[4])
q = q + a
ch = ch + b
if b > a:
more = more + 1
if b < a:
less = less + 1
print(" " + pad(e[0], 25) + lpad(money(a), 9) + lpad(money(b), 10) + lpad(signed(b - a), 12))
print("Quoted in all " + money(q) + ", charged " + money(ch) + ": " + signed(ch - q) + ".")
print(str(more) + " lines were charged more than their quote and " + str(less) + " less. Only those charged more get written in about, so the complaints see one side of a difference that runs both ways.")
def price(t, top):
dot = 0
i = 0
at = 0
for c in t:
if c == ".":
dot = dot + 1
at = i
elif c < "0" or c > "9":
return 0 - 1
i = i + 1
if dot == 0:
if len(t) < 1 or len(t) > top:
return 0 - 1
return int(t) * 100
if dot > 1 or at < 1 or at > top or len(t) - at - 1 < 1 or len(t) - at - 1 > 2:
return 0 - 1
f = t[at + 1:]
if len(f) == 1:
f = f + "0"
return int(t[0:at]) * 100 + int(f)
def own():
t = plain(input("currency (USD, JPY, GBP or EUR)> "))
if t == "":
print("Cancelled.")
return
cur = ""
k = 0
for c in ["USD", "JPY", "GBP", "EUR"]:
if t == c or t == ["usd", "jpy", "gbp", "eur"][k]:
cur = c
k = k + 1
if cur == "":
print("Type USD, JPY, GBP or EUR.")
return
if cur == "JPY":
a = plain(input("amount in yen (1 to 9999999)> "))
if a == "":
print("Cancelled.")
return
if len(a) > 7 or not digits(a) or int(a) < 1:
print("Type a whole number of yen from 1 to 9999999.")
return
m = int(a)
else:
a = plain(input("amount (0.01 to 99999.99)> "))
if a == "":
print("Cancelled.")
return
m = price(a, 5)
if m < 1:
print("Type an amount such as 12.50, from 0.01 to 99999.99.")
return
d1 = plain(input("day authorized (1 to 10)> "))
if d1 == "":
print("Cancelled.")
return
if len(d1) > 2 or not digits(d1) or int(d1) < 1 or int(d1) > 10:
print("Type a whole number from 1 to 10.")
return
a1 = int(d1)
d2 = plain(input("day captured (" + str(a1) + " to 10)> "))
if d2 == "":
print("Cancelled.")
return
if len(d2) > 2 or not digits(d2) or int(d2) < a1 or int(d2) > 10:
print("Type a whole number from " + str(a1) + " to 10.")
return
a2 = int(d2)
qa = fx.cents(cur, m, a1)
cb = fx.cents(cur, m, a2)
print("Quoted at authorization, day " + str(a1) + ": " + money(qa) + "; charged at capture, day " + str(a2) + ": " + money(cb) + ".")
if cb > qa:
print(signed(cb - qa) + ": charged more than the quote the traveller agreed to.")
elif cb < qa:
print(signed(cb - qa) + ": charged less than the quote, and nobody writes in about that.")
else:
print("The charge is the quote.")
print("== Travel expenses ==")
print("A trip paid in four currencies and reimbursed in dollars.")
running = True
while running:
print("")
print("1) the expenses 2) the rates 3) totals 4) quote and charge 5) an expense of your own 6) quit")
choice = plain(input("choice> "))
if choice == "1":
show_expenses()
elif choice == "2":
show_rates()
elif choice == "3":
show_totals()
elif choice == "4":
show_card()
elif choice == "5":
own()
elif choice == "6":
running = False
else:
print("Pick a number from 1 to 6.")
print("Bye.")
fx.eml
eml# P164 travel expenses - a ten-day trip paid in four currencies and
# reimbursed in dollars. As in the corpus case
# the-total-was-checked-and-the-currencies-were-mixed, the amount column
# holds yen, pence, euro cents and cents, and adding it gives a number in
# no currency that a check against a stored sum of the same column passes.
# As in the-amounts-were-summed-at-each-days-rate, each line converted at
# its own day's rate is right and their sum mixes ten rates. And as in
# the-exchange-rate-was-right-and-its-timestamp-was-not, a card payment is
# quoted at authorization and charged at capture, two correct rates from two
# moments.
def currencies():
# [code, decimal places of its minor unit]
return [["USD", 2], ["JPY", 0], ["GBP", 2], ["EUR", 2]]
def places(cur):
for c in currencies():
if c[0] == cur:
return c[1]
return 2
def rate(cur, d):
# dollars for one unit of cur on day d (1 to 10), in millionths
if cur == "JPY":
return [6600, 6620, 6585, 6640, 6610, 6655, 6670, 6650, 6690, 6705][d - 1]
if cur == "GBP":
return [1260000, 1264000, 1261500, 1266000, 1270500, 1268000, 1272000, 1269500, 1275000, 1279000][d - 1]
if cur == "EUR":
return [1080000, 1082500, 1079000, 1084000, 1087000, 1083500, 1088000, 1090500, 1086000, 1092500][d - 1]
return 1000000
def expenses():
# [item, currency, amount in its minor unit, day authorized, day captured]
return [["flight", "USD", 84200, 1, 1],
["hotel, Tokyo, 3 nights", "JPY", 96000, 2, 5],
["dinner, Tokyo", "JPY", 12800, 3, 4],
["hotel, London, 3 nights", "GBP", 54000, 5, 9],
["taxi, London", "GBP", 3850, 6, 7],
["hotel, Paris, 2 nights", "EUR", 41000, 8, 10],
["dinner, Paris", "EUR", 9640, 9, 10]]
def cents(cur, m, d):
# m minor units of cur, in dollar cents at day d's rate, rounded half-up
1 => den
0 => k
while k < places(cur) + 4:
den * 10 => den
k + 1 => k
m * rate(cur, d) * 2 + den => x
return int((x - x % (2 * den)) / (2 * den))
Python projection (fx.py)
def currencies():
return [["USD", 2], ["JPY", 0], ["GBP", 2], ["EUR", 2]]
def places(cur):
for c in currencies():
if c[0] == cur:
return c[1]
return 2
def rate(cur, d):
if cur == "JPY":
return [6600, 6620, 6585, 6640, 6610, 6655, 6670, 6650, 6690, 6705][d - 1]
if cur == "GBP":
return [1260000, 1264000, 1261500, 1266000, 1270500, 1268000, 1272000, 1269500, 1275000, 1279000][d - 1]
if cur == "EUR":
return [1080000, 1082500, 1079000, 1084000, 1087000, 1083500, 1088000, 1090500, 1086000, 1092500][d - 1]
return 1000000
def expenses():
return [["flight", "USD", 84200, 1, 1], ["hotel, Tokyo, 3 nights", "JPY", 96000, 2, 5], ["dinner, Tokyo", "JPY", 12800, 3, 4], ["hotel, London, 3 nights", "GBP", 54000, 5, 9], ["taxi, London", "GBP", 3850, 6, 7], ["hotel, Paris, 2 nights", "EUR", 41000, 8, 10], ["dinner, Paris", "EUR", 9640, 9, 10]]
def cents(cur, m, d):
den = 1
k = 0
while k < places(cur) + 4:
den = den * 10
k = k + 1
x = m * rate(cur, d) * 2 + den
return int((x - x % (2 * den)) / (2 * den))