Float sums
Four workers' partial sums added as they arrive: a loop and Kahan's sum give five different totals over the 24 orders, and Neumaier's sum the true one in all of them.
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 reducer that adds four workers' partial sums as they arrive, and what the total depends on.
main.eml- the menu, the partials, every arrival order, ten values of 0.1, and numbers of your ownsums.eml- the partials; a plain loop, Kahan's compensated sum and Neumaier's; every order of a list; distinct values
How each part works:
- The partials are 1e+16 of inflows, -1e+16 of outflows, 1.0 of fees and 0.5 of interest; the true total is 1.5.
- As in the corpus case
the-order-the-workers-finished-changed-the-total, floating-point addition is not associative: next to 1e+16 a 1.0 or a 0.5 is lost, and which are lost depends on the order. Over the 24 arrival orders the loop gives 5 different totals, 0.0 to 2.0, and 1.5 in 4. - As in
the-compensated-sum-and-the-loop-disagree, a compensated sum keeps what each addition loses. Kahan's correction is exact only while the running total is the larger number, and is folded into the next number, so here it does no better than the loop: 5 totals, 1.5 in 4. Neumaier's keeps what is lost apart and adds it at the end: 1.5 in all 24. Ten values of 0.1 come to 0.9999999999999999 in the loop and 1.0 in both compensated sums; the loop parts from them at six values.
What is checked: menu choices 1 to 5; 2 to 6 numbers such as 3, -2.5, 1e16 or 7E+2, separated by spaces, with at most 17 digits on either side of the point and 2 in the exponent. An empty answer cancels.
Sessions: sessions/basic.in gives: - the partials, every arrival order and the ten values of 0.1; - numbers of your own: 1e16 3 -1e16 2, then 0.1 0.2 0.3, then 1 2.
sessions/bad-input.in gives: - menu choices 0 and x; - numbers: a cancel, one number, seven numbers, a letter, 1., .5, an exponent of 100, --1, 1,5 and eighteen digits; - -2.5 1e-3 7E+2.
Built on the verified corpus cases the-order-the-workers-finished-changed-the-total (three partial sums added in arrival order, every order enumerated) and the-compensated-sum-and-the-loop-disagree (ten values of 0.1 added by a loop and by a compensated sum).
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== Float sums ==
Partial sums added as they arrive, and what the total depends on.
1) the partials 2) every arrival order 3) ten values of 0.1 4) numbers of your own 5) quit
choice> 0
Pick a number from 1 to 5.
1) the partials 2) every arrival order 3) ten values of 0.1 4) numbers of your own 5) quit
choice> x
Pick a number from 1 to 5.
1) the partials 2) every arrival order 3) ten values of 0.1 4) numbers of your own 5) quit
choice> 4
2 to 6 numbers, separated by spaces>
Cancelled.
1) the partials 2) every arrival order 3) ten values of 0.1 4) numbers of your own 5) quit
choice> 4
2 to 6 numbers, separated by spaces> 5
Type 2 to 6 numbers such as 3, -2.5 or 1e16, separated by spaces.
1) the partials 2) every arrival order 3) ten values of 0.1 4) numbers of your own 5) quit
choice> 4
2 to 6 numbers, separated by spaces> 1 2 3 4 5 6 7
Type 2 to 6 numbers such as 3, -2.5 or 1e16, separated by spaces.
1) the partials 2) every arrival order 3) ten values of 0.1 4) numbers of your own 5) quit
choice> 4
2 to 6 numbers, separated by spaces> 1 x
Type 2 to 6 numbers such as 3, -2.5 or 1e16, separated by spaces.
1) the partials 2) every arrival order 3) ten values of 0.1 4) numbers of your own 5) quit
choice> 4
2 to 6 numbers, separated by spaces> 1.
Type 2 to 6 numbers such as 3, -2.5 or 1e16, separated by spaces.
1) the partials 2) every arrival order 3) ten values of 0.1 4) numbers of your own 5) quit
choice> 4
2 to 6 numbers, separated by spaces> .5 1
Type 2 to 6 numbers such as 3, -2.5 or 1e16, separated by spaces.
1) the partials 2) every arrival order 3) ten values of 0.1 4) numbers of your own 5) quit
choice> 4
2 to 6 numbers, separated by spaces> 1e100 2
Type 2 to 6 numbers such as 3, -2.5 or 1e16, separated by spaces.
1) the partials 2) every arrival order 3) ten values of 0.1 4) numbers of your own 5) quit
choice> 4
2 to 6 numbers, separated by spaces> --1 2
Type 2 to 6 numbers such as 3, -2.5 or 1e16, separated by spaces.
1) the partials 2) every arrival order 3) ten values of 0.1 4) numbers of your own 5) quit
choice> 4
2 to 6 numbers, separated by spaces> 1,5 2
Type 2 to 6 numbers such as 3, -2.5 or 1e16, separated by spaces.
1) the partials 2) every arrival order 3) ten values of 0.1 4) numbers of your own 5) quit
choice> 4
2 to 6 numbers, separated by spaces> 123456789012345678 1
Type 2 to 6 numbers such as 3, -2.5 or 1e16, separated by spaces.
1) the partials 2) every arrival order 3) ten values of 0.1 4) numbers of your own 5) quit
choice> 4
2 to 6 numbers, separated by spaces> -2.5 1e-3 7E+2
In the 6 orders of these numbers: the loop always gives 697.501; Kahan's sum always gives 697.501; Neumaier's sum always gives 697.501.
1) the partials 2) every arrival order 3) ten values of 0.1 4) numbers of your own 5) quit
choice> 5
Bye.
What was typed (25 lines)
0
x
4
4
5
4
1 2 3 4 5 6 7
4
1 x
4
1.
4
.5 1
4
1e100 2
4
--1 2
4
1,5 2
4
123456789012345678 1
4
-2.5 1e-3 7E+2
5
basic
interpreter: byte-equal== Float sums ==
Partial sums added as they arrive, and what the total depends on.
1) the partials 2) every arrival order 3) ten values of 0.1 4) numbers of your own 5) quit
choice> 1
Four workers sum a day's account movements, and the reducer adds their partials as they arrive:
inflows 1e+16
outflows -1e+16
fees 1.0
interest 0.5
The true total is 1.5: the inflows and the outflows cancel.
1) the partials 2) every arrival order 3) ten values of 0.1 4) numbers of your own 5) quit
choice> 2
arrival order loop Kahan Neumaier
inflows, outflows, fees, interest 1.5 1.5 1.5
inflows, outflows, interest, fees 1.5 1.5 1.5
inflows, fees, outflows, interest 0.5 0.5 1.5
inflows, fees, interest, outflows 0.0 2.0 1.5
inflows, interest, outflows, fees 1.0 1.0 1.5
inflows, interest, fees, outflows 0.0 2.0 1.5
outflows, inflows, fees, interest 1.5 1.5 1.5
outflows, inflows, interest, fees 1.5 1.5 1.5
outflows, fees, inflows, interest 0.5 0.5 1.5
outflows, fees, interest, inflows 0.0 2.0 1.5
outflows, interest, inflows, fees 1.0 1.0 1.5
outflows, interest, fees, inflows 0.0 2.0 1.5
fees, inflows, outflows, interest 0.5 0.5 1.5
fees, inflows, interest, outflows 0.0 0.0 1.5
fees, outflows, inflows, interest 0.5 0.5 1.5
fees, outflows, interest, inflows 0.0 0.0 1.5
fees, interest, inflows, outflows 2.0 2.0 1.5
fees, interest, outflows, inflows 2.0 2.0 1.5
interest, inflows, outflows, fees 1.0 1.0 1.5
interest, inflows, fees, outflows 0.0 0.0 1.5
interest, outflows, inflows, fees 1.0 1.0 1.5
interest, outflows, fees, inflows 0.0 0.0 1.5
interest, fees, inflows, outflows 2.0 2.0 1.5
interest, fees, outflows, inflows 2.0 2.0 1.5
The loop: 5 different totals, and 1.5 in 4 of the 24 orders. The answer is a function of which worker finished first.
Kahan's sum: 5 different totals, 1.5 in 4. Its correction is exact only while the running total is the larger number, and it is folded into the next number: next to 1e+16 the small partials are lost either way.
Neumaier's sum: 1.5 in 24 of the 24 orders. It keeps what each addition loses apart from the total, and adds it once, at the end.
1) the partials 2) every arrival order 3) ten values of 0.1 4) numbers of your own 5) quit
choice> 3
Ten values of 0.1, added one by one:
values loop Kahan Neumaier
1 0.1 0.1 0.1
2 0.2 0.2 0.2
3 0.30000000000000004 0.30000000000000004 0.30000000000000004
4 0.4 0.4 0.4
5 0.5 0.5 0.5
6 0.6 0.6000000000000001 0.6000000000000001
7 0.7 0.7000000000000001 0.7000000000000001
8 0.7999999999999999 0.8 0.8
9 0.8999999999999999 0.9 0.9
10 0.9999999999999999 1.0 1.0
The loop parts from the compensated sums at 6 values, and ten of them come to 0.9999999999999999, which is not 1.0. Kept in whole cents, 10 + 10 + ... is 100 exactly.
1) the partials 2) every arrival order 3) ten values of 0.1 4) numbers of your own 5) quit
choice> 4
2 to 6 numbers, separated by spaces> 1e16 3 -1e16 2
In the 24 orders of these numbers: the loop gives 3 different totals, from 4.0 to 6.0; Kahan's sum gives 3 different totals, from 4.0 to 6.0; Neumaier's sum always gives 5.0.
1) the partials 2) every arrival order 3) ten values of 0.1 4) numbers of your own 5) quit
choice> 4
2 to 6 numbers, separated by spaces> 0.1 0.2 0.3
In the 6 orders of these numbers: the loop gives 2 different totals, from 0.6 to 0.6000000000000001; Kahan's sum always gives 0.6; Neumaier's sum always gives 0.6.
1) the partials 2) every arrival order 3) ten values of 0.1 4) numbers of your own 5) quit
choice> 4
2 to 6 numbers, separated by spaces> 1 2
In the 2 orders of these numbers: the loop always gives 3.0; Kahan's sum always gives 3.0; Neumaier's sum always gives 3.0.
1) the partials 2) every arrival order 3) ten values of 0.1 4) numbers of your own 5) quit
choice> 5
Bye.
What was typed (10 lines)
1
2
3
4
1e16 3 -1e16 2
4
0.1 0.2 0.3
4
1 2
5
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# P169 float sums: four workers' partials added as they arrive - the
# partials, every arrival order through a loop, Kahan's sum and Neumaier's,
# ten values of 0.1, and numbers of your own.
import sums
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 joined(names):
"" => out
for n in names:
if out != "":
out + ", " => out
out + n => out
return out
def show_partials():
"Four workers sum a day's account movements, and the reducer adds their partials as they arrive:" ^0
for p in sums.partials():
(" " + pad(p[0], 10) + lpad(str(p[1]), 8)) ^0
"The true total is 1.5: the inflows and the outflows cancel." ^0
def tally(totals, want):
# [different totals, how many equal want]
0 => right
for t in totals:
if t == want:
right + 1 => right
return [len(sums.distinct(totals)), right]
def show_orders():
sums.partials() => ps
(" " + pad("arrival order", 38) + lpad("loop", 6) + lpad("Kahan", 8) + lpad("Neumaier", 10)) ^0
[] => a
[] => b
[] => c
for o in sums.orders([0, 1, 2, 3]):
[] => names
[] => xs
for i in o:
names + [ps[i][0]] => names
xs + [ps[i][1]] => xs
sums.loop(xs) => x
sums.kahan(xs) => y
sums.neumaier(xs) => z
a + [x] => a
b + [y] => b
c + [z] => c
(" " + pad(joined(names), 38) + lpad(str(x), 6) + lpad(str(y), 8) + lpad(str(z), 10)) ^0
tally(a, 1.5) => ta
tally(b, 1.5) => tb
tally(c, 1.5) => tc
("The loop: " + str(ta[0]) + " different totals, and 1.5 in " + str(ta[1]) + " of the 24 orders. The answer is a function of which worker finished first.") ^0
("Kahan's sum: " + str(tb[0]) + " different totals, 1.5 in " + str(tb[1]) + ". Its correction is exact only while the running total is the larger number, and it is folded into the next number: next to 1e+16 the small partials are lost either way.") ^0
("Neumaier's sum: 1.5 in " + str(tc[1]) + " of the 24 orders. It keeps what each addition loses apart from the total, and adds it once, at the end.") ^0
def show_tenths():
"Ten values of 0.1, added one by one:" ^0
(" " + pad("values", 8) + pad("loop", 22) + pad("Kahan", 22) + "Neumaier") ^0
[] => xs
0 => first
0 => k
while k < 10:
xs + [0.1] => xs
sums.loop(xs) => x
sums.neumaier(xs) => z
(" " + pad(str(k + 1), 8) + pad(str(x), 22) + pad(str(sums.kahan(xs)), 22) + str(z)) ^0
if first == 0 and x != z:
k + 1 => first
k + 1 => k
("The loop parts from the compensated sums at " + str(first) + " values, and ten of them come to " + str(sums.loop(xs)) + ", which is not 1.0. Kept in whole cents, 10 + 10 + ... is 100 exactly.") ^0
def number(t):
# True when t is a number such as 2, -3.5, 1e16 or 2.5e-3
0 => i
if len(t) > 0 and t[0] == "-":
1 => i
i => a
while i < len(t) and t[i] >= "0" and t[i] <= "9":
i + 1 => i
if i == a or i - a > 17:
return False
if i < len(t) and t[i] == ".":
i + 1 => i
i => a
while i < len(t) and t[i] >= "0" and t[i] <= "9":
i + 1 => i
if i == a or i - a > 17:
return False
if i < len(t) and (t[i] == "e" or t[i] == "E"):
i + 1 => i
if i < len(t) and (t[i] == "-" or t[i] == "+"):
i + 1 => i
i => a
while i < len(t) and t[i] >= "0" and t[i] <= "9":
i + 1 => i
if i == a or i - a > 2:
return False
return i == len(t)
def words(t):
[] => out
"" => cur
for c in t + " ":
if c == " ":
if cur != "":
out + [cur] => out
"" => cur
else:
cur + c => cur
return out
def describe(name, totals):
sums.distinct(totals) => d
if len(d) == 1:
return name + " always gives " + str(d[0])
d[0] => lo
d[0] => hi
for v in d:
if v < lo:
v => lo
if v > hi:
v => hi
return name + " gives " + str(len(d)) + " different totals, from " + str(lo) + " to " + str(hi)
def own():
plain(input("2 to 6 numbers, separated by spaces> ")) => t
if t == "":
"Cancelled." ^0
return
words(t) => ws
True => ok
if len(ws) < 2 or len(ws) > 6:
False => ok
for w in ws:
if not number(w):
False => ok
if not ok:
"Type 2 to 6 numbers such as 3, -2.5 or 1e16, separated by spaces." ^0
return
[] => xs
for w in ws:
xs + [float(w)] => xs
[] => a
[] => b
[] => c
0 => n
for o in sums.orders(xs):
a + [sums.loop(o)] => a
b + [sums.kahan(o)] => b
c + [sums.neumaier(o)] => c
n + 1 => n
("In the " + str(n) + " orders of these numbers: " + describe("the loop", a) + "; " + describe("Kahan's sum", b) + "; " + describe("Neumaier's sum", c) + ".") ^0
"== Float sums ==" ^0
"Partial sums added as they arrive, and what the total depends on." ^0
True => running
while running:
"" ^0
"1) the partials 2) every arrival order 3) ten values of 0.1 4) numbers of your own 5) quit" ^0
plain(input("choice> ")) => choice
if choice == "1":
show_partials()
elif choice == "2":
show_orders()
elif choice == "3":
show_tenths()
elif choice == "4":
own()
elif choice == "5":
False => running
else:
"Pick a number from 1 to 5." ^0
"Bye." ^0
Python projection (main.py)
import sums
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 joined(names):
out = ""
for n in names:
if out != "":
out = out + ", "
out = out + n
return out
def show_partials():
print("Four workers sum a day's account movements, and the reducer adds their partials as they arrive:")
for p in sums.partials():
print(" " + pad(p[0], 10) + lpad(str(p[1]), 8))
print("The true total is 1.5: the inflows and the outflows cancel.")
def tally(totals, want):
right = 0
for t in totals:
if t == want:
right = right + 1
return [len(sums.distinct(totals)), right]
def show_orders():
ps = sums.partials()
print(" " + pad("arrival order", 38) + lpad("loop", 6) + lpad("Kahan", 8) + lpad("Neumaier", 10))
a = []
b = []
c = []
for o in sums.orders([0, 1, 2, 3]):
names = []
xs = []
for i in o:
names = names + [ps[i][0]]
xs = xs + [ps[i][1]]
x = sums.loop(xs)
y = sums.kahan(xs)
z = sums.neumaier(xs)
a = a + [x]
b = b + [y]
c = c + [z]
print(" " + pad(joined(names), 38) + lpad(str(x), 6) + lpad(str(y), 8) + lpad(str(z), 10))
ta = tally(a, 1.5)
tb = tally(b, 1.5)
tc = tally(c, 1.5)
print("The loop: " + str(ta[0]) + " different totals, and 1.5 in " + str(ta[1]) + " of the 24 orders. The answer is a function of which worker finished first.")
print("Kahan's sum: " + str(tb[0]) + " different totals, 1.5 in " + str(tb[1]) + ". Its correction is exact only while the running total is the larger number, and it is folded into the next number: next to 1e+16 the small partials are lost either way.")
print("Neumaier's sum: 1.5 in " + str(tc[1]) + " of the 24 orders. It keeps what each addition loses apart from the total, and adds it once, at the end.")
def show_tenths():
print("Ten values of 0.1, added one by one:")
print(" " + pad("values", 8) + pad("loop", 22) + pad("Kahan", 22) + "Neumaier")
xs = []
first = 0
k = 0
while k < 10:
xs = xs + [0.1]
x = sums.loop(xs)
z = sums.neumaier(xs)
print(" " + pad(str(k + 1), 8) + pad(str(x), 22) + pad(str(sums.kahan(xs)), 22) + str(z))
if first == 0 and x != z:
first = k + 1
k = k + 1
print("The loop parts from the compensated sums at " + str(first) + " values, and ten of them come to " + str(sums.loop(xs)) + ", which is not 1.0. Kept in whole cents, 10 + 10 + ... is 100 exactly.")
def number(t):
i = 0
if len(t) > 0 and t[0] == "-":
i = 1
a = i
while i < len(t) and t[i] >= "0" and t[i] <= "9":
i = i + 1
if i == a or i - a > 17:
return False
if i < len(t) and t[i] == ".":
i = i + 1
a = i
while i < len(t) and t[i] >= "0" and t[i] <= "9":
i = i + 1
if i == a or i - a > 17:
return False
if i < len(t) and (t[i] == "e" or t[i] == "E"):
i = i + 1
if i < len(t) and (t[i] == "-" or t[i] == "+"):
i = i + 1
a = i
while i < len(t) and t[i] >= "0" and t[i] <= "9":
i = i + 1
if i == a or i - a > 2:
return False
return i == len(t)
def words(t):
out = []
cur = ""
for c in t + " ":
if c == " ":
if cur != "":
out = out + [cur]
cur = ""
else:
cur = cur + c
return out
def describe(name, totals):
d = sums.distinct(totals)
if len(d) == 1:
return name + " always gives " + str(d[0])
lo = d[0]
hi = d[0]
for v in d:
if v < lo:
lo = v
if v > hi:
hi = v
return name + " gives " + str(len(d)) + " different totals, from " + str(lo) + " to " + str(hi)
def own():
t = plain(input("2 to 6 numbers, separated by spaces> "))
if t == "":
print("Cancelled.")
return
ws = words(t)
ok = True
if len(ws) < 2 or len(ws) > 6:
ok = False
for w in ws:
if not number(w):
ok = False
if not ok:
print("Type 2 to 6 numbers such as 3, -2.5 or 1e16, separated by spaces.")
return
xs = []
for w in ws:
xs = xs + [float(w)]
a = []
b = []
c = []
n = 0
for o in sums.orders(xs):
a = a + [sums.loop(o)]
b = b + [sums.kahan(o)]
c = c + [sums.neumaier(o)]
n = n + 1
print("In the " + str(n) + " orders of these numbers: " + describe("the loop", a) + "; " + describe("Kahan's sum", b) + "; " + describe("Neumaier's sum", c) + ".")
print("== Float sums ==")
print("Partial sums added as they arrive, and what the total depends on.")
running = True
while running:
print("")
print("1) the partials 2) every arrival order 3) ten values of 0.1 4) numbers of your own 5) quit")
choice = plain(input("choice> "))
if choice == "1":
show_partials()
elif choice == "2":
show_orders()
elif choice == "3":
show_tenths()
elif choice == "4":
own()
elif choice == "5":
running = False
else:
print("Pick a number from 1 to 5.")
print("Bye.")
sums.eml
eml# P169 float sums - a reducer that adds the partial sums of four workers as
# they arrive. As in the corpus case
# the-order-the-workers-finished-changed-the-total, floating-point addition
# is not associative: where the partials differ greatly in size, a small one
# added to a large one is lost, and which are lost depends on the order. And
# as in the-compensated-sum-and-the-loop-disagree, a compensated sum keeps
# the part each step would lose: Kahan's while the running total is the
# larger number, Neumaier's whichever it is.
def partials():
# [what the worker summed, its partial]
return [["inflows", 10000000000000000.0], ["outflows", -10000000000000000.0], ["fees", 1.0], ["interest", 0.5]]
def absf(x):
if x < 0:
return 0 - x
return x
def loop(xs):
0.0 => t
for x in xs:
t + x => t
return t
def kahan(xs):
0.0 => s
0.0 => c
for x in xs:
x - c => y
s + y => t
(t - s) - y => c
t => s
return s
def neumaier(xs):
0.0 => s
0.0 => c
for x in xs:
s + x => t
if absf(s) >= absf(x):
c + ((s - t) + x) => c
else:
c + ((x - t) + s) => c
t => s
return s + c
def orders(items):
# every order of items, in the order itertools would list them
if len(items) <= 1:
return [items]
[] => out
0 => i
while i < len(items):
items[0:i] + items[i + 1:] => rest
for p in orders(rest):
out + [[items[i]] + p] => out
i + 1 => i
return out
def distinct(values):
# the values in the order they first appear, each once
[] => out
for v in values:
False => found
for u in out:
if u == v:
True => found
if not found:
out + [v] => out
return out
Python projection (sums.py)
def partials():
return [["inflows", 10000000000000000.0], ["outflows", -10000000000000000.0], ["fees", 1.0], ["interest", 0.5]]
def absf(x):
if x < 0:
return 0 - x
return x
def loop(xs):
t = 0.0
for x in xs:
t = t + x
return t
def kahan(xs):
s = 0.0
c = 0.0
for x in xs:
y = x - c
t = s + y
c = t - s - y
s = t
return s
def neumaier(xs):
s = 0.0
c = 0.0
for x in xs:
t = s + x
if absf(s) >= absf(x):
c = c + (s - t + x)
else:
c = c + (x - t + s)
s = t
return s + c
def orders(items):
if len(items) <= 1:
return [items]
out = []
i = 0
while i < len(items):
rest = items[0:i] + items[i + 1:]
for p in orders(rest):
out = out + [[items[i]] + p]
i = i + 1
return out
def distinct(values):
out = []
for v in values:
found = False
for u in out:
if u == v:
found = True
if not found:
out = out + [v]
return out