Due to precision limitations in floating point numbers, the order in which numbers are summed can affect the result.
>>> 0.3 + 0.4 + 2.8
3.5
>>> 2.8 + 0.4 + 0.3
3.4999999999999996
This small error can become a bigger problem if the results are then rounded.
>>> round(0.3 + 0.4 + 2.8)
4
>>> round(2.8 + 0.4 + 0.3)
3
I would like to generate a list of random floats such that their rounded sum does not depend on the order in which the numbers are summed. My current brute force approach is O(n!). Is there a more efficient method?
import random
import itertools
import math
def gen_sum_safe_seq(func, length: int, precision: int) -> list[float]:
"""
Return a list of floats that has the same sum when rounded to the given
precision regardless of the order in which its values are summed.
"""
invalid = True
while invalid:
invalid = False
nums = [func() for _ in range(length)]
first_sum = round(sum(nums), precision)
for p in itertools.permutations(nums):
if round(sum(p), precision) != first_sum:
invalid = True
print(f"rejected {nums}")
break
return nums
for _ in range(3):
nums = gen_sum_safe_seq(
func=lambda :round(random.gauss(3, 0.5), 3),
length=10,
precision=2,
)
print(f"{nums} sum={sum(nums)}")
For context, as part of a programming exercise I'm providing a list of floats that model a measured value over time to ~1000 entry-level programming students. They will sum them in a variety of ways. Provided that their code is correct, I'd like for them all to get the same result to simplify checking their code. I do not want to introduce the complexities of floating point representation to students at this level.