1

Assuming 2 digits after decimal visual representation of floating point number (eg: currency), what's the best way to split an amount with minimum total error?

For example, to distribute 100.00 equally to 3 bank accounts, I'd do something like this:

double amount = 100.0;
double acc1 = amount / 3.0;
double acc2 = amount / 3.0;
double acc3 = amount / 3.0;

However when printing each of the account balance with 2 decimal, I get:

printf("%.2f\n", acc1);
printf("%.2f\n", acc2);
printf("%.2f\n", acc3);

33.33
33.33
33.33

It's apparent summing up all of the account's amount gives 99.99, a 0.01 is lost due to rounding.

Ideally I want some function / algorithm that can distribute almost-equally and visually print

33.34
33.33
33.33

It doesn't matter which of the three accounts get the extra 0.01.

How do I do this? Is there any rounding algorithm name for this?

gerrytan
  • 40,313
  • 9
  • 84
  • 99
  • 1
    Floating-point is [generally not appropriate for currency values](http://spin.atomicobject.com/2014/08/14/currency-rounding-errors/). Even when the display is rounded to two fractional digits, there can be smaller errors that accumulate over the course of many calculations and eventually affect the displayed result; in particular, [one-tenth can't be accurately represented](http://www.exploringbinary.com/why-0-point-1-does-not-exist-in-floating-point/). Use a fixed-point type instead. – Wyzard Oct 28 '14 at 04:12
  • 4
    Is `double acc3 = amount - (acc1 + acc2);` not an option? – imreal Oct 28 '14 at 04:13
  • @user2899162 Correct, I'm only interested in storing the amount for 2 decimal visual representation only – gerrytan Oct 28 '14 at 04:13
  • @imreal that leaves (to floating point precision) `33.33` everywhere, unless the numbers are rounded first. Anyways, I'd work with integers only, you can make this problem much easier that way. EDIT: If you don't round first (and are working with floating point numbers when you shouldn't be) then you get the following: http://ideone.com/j3nkFK – druckermanly Oct 28 '14 at 04:14
  • 2
    If you generalize the question http://stackoverflow.com/questions/17641300/rounding-floats-so-that-they-sum-to-precisely-1/17641358#17641358 to “round floats so that they sum up to precisely 100”, and apply the solution to your numbers, you have reduced the problem to computing X3 as 100 - X1 - X2. – Pascal Cuoq Oct 28 '14 at 06:43

3 Answers3

6

You are making multiple errors here. double is a double-precision binary floating-point number. 100.0 / 3.0 equals 33.33333333333333570180911920033395290374755859375; the problem with giving everybody 100.0/3.0 is not that you are giving everyone slightly less than the full amount of cake, but rather you are trying to give everyone more cake than you have. You then round this to two decimal places, which is an operation you cannot reasonably expect to preserve the sum.

I would suggest trafficking in integer numbers of cents instead of floating-point numbers of dollars in your application.

With that said, to split a cake of size C into floating-point parts for distribution among n people, you can give n-1 people pieces of size C/n and the last person a piece of size fma(-C/n, n-1, C). The fused multiply-add is necessary here since the multiplication in (C/n)*(n-1) may incur roundoff error. One can also accomplish this using fmod or remainder.

tmyklebu
  • 13,915
  • 3
  • 28
  • 57
  • This solution uses neither FMA nor `fmod`: http://stackoverflow.com/a/17641358/139746. You can either put all the correction in the last Xn, or recompute the ideal fraction of the remaining cake periodically (say, at the time of computing each Xi). – Pascal Cuoq Oct 28 '14 at 06:40
  • Cool! I had a feeling that this was a duplicate of something. Can you dupehammer this? – tmyklebu Oct 28 '14 at 06:45
  • I can (gold C badge), but it is not exactly the same question, just related. The last time I dupehammered a clear duplicate of an uninteresting question, I never heard the end of it because the C++ crowd thought it was interesting and valid and new and I had been a jerk for so long on StackOverflow and this was a marvellous chance to let me know (and guess what other tag THIS question has). – Pascal Cuoq Oct 28 '14 at 06:55
  • I see. I don't understand why "your question has been closed as duplicate" gets interpreted as "you're a bastard for not finding this other question" instead of as "you can find the answer to your question here." – tmyklebu Oct 28 '14 at 07:54
2

Using floating-point numbers to represent currency is just asking for trouble; integers would be much more appropriate for this purpose.

For this problem, you want to first distribute original sum x to n people using floored division (round downwards to the nearest cent):

int q = x / n;

(Here I'm assuming integer arithmetic with cents.) In the case of x = 10000 and n = 3, you calculate the quotient q = 10000 / 3 = 3333 and give that to everyone:

33.33
33.33
33.33

Then, calculate the remainder:

int r = x % n;

and distribute the remaining r cents to r people (give them one each). Since r is always less than the number of people, you will need to decide who gets to be the lucky winners.

In the example, r = 10000 % 3 = 1, so we have one cent that can only be given to one person:

33.34 = 33.33 + 0.01
33.33
33.33

You can also calculate this directly using an explicit formula. The number of cents that the i-th person receives is given by:

y[i] = q + (i < r);

where 0 ≤ i < n.

Community
  • 1
  • 1
Rufflewind
  • 8,545
  • 2
  • 35
  • 55
0

Since addition is not even associative in floating point arithmetic, your problem is not well defined...
(Well it might be well defined, see update 2, but some naive expectations might be a cause of surprise)

If you have (x+y) + z == sum, there is not much guaranty that x + (y+z) == sum

For example, say you want to split 0.31 in 3 IEEE 754 double precision, 0.10, 0.10 and 0.11
Here is sum computed back (the shortest decimal fraction that would round to the same float as the sum)

0.10+0.10+0.11 == 0.31
0.11+0.10+0.10 == 0.31000000000000005

These two numbers are very close, but not the same, and no one is really equal to 31/100 but well...
In the second form, you could do a slight correction:

0.10999999999999999 + 0.10 + 0.10 == 0.31

That means that depending on the order you use to sum back, results shall differ...
So what do you expect exactly from floating point?

Of course, you could just ignore those tiny differences and continue using floating point to do the necessary quotient/remainder operations if you are sure that rounding errors will never accumulate dangerously; it's possible to use float but you should then question each and every naive expectation and it can get tricky. My advice would be to simply use integer arithmetic as others suggested.

UPDATE

After reading the link Rounding floats so that they sum to precisely 1 from PascalCuoq, I realized that 0.10999999999999999 is in all case a better solution than 0.11 because it's 0.31-(0.31-0.11), and because 0.31-(0.31-0.1)==0.1. Thanks PascalCuoq!

UPDATE 2

The non associativity is essential because it implies that two different naive implementations will lead to different results.

However, as underlined by tmyklebu, the exact sum is well defined, though not easily accessible in C/C++.

In above example, the exact sum of nearest double to 10/100 , 10/100 , 11/100 slightly excess the nearest double to 31/100, so we have to adjust one of the operands down by an ulp. This is what makes floating point usage arguable more than the single property of associativity.

This can be illustrated if we try to fairly distribute the double amount=0.30 in 3 double: because the nearest double to 30/100 is exactly 5404319552844595/18014398509481984, and since the numerator is not divisible by 3 (the remainder is 1), then it cannot be split in 3 equal doubles. The quotient of 5404319552844595/3 divided by 18014398509481984 will lead to the predecessor of 0.1, and we have to adjust one operand by increasing with an ulp. Naive sum hereunder matches exact sum:

0.09999999999999999+0.09999999999999999+0.1 == 0.3 

This is another form of the very well known (at least on SO) 0.1+0.1+0.1 != 0.3

Community
  • 1
  • 1
aka.nice
  • 9,100
  • 1
  • 28
  • 40
  • One can speak of the sum of a list of floating-point numbers. This sum is a real number and is well-defined. It does not necessarily correspond to adding the numbers up using floating-point addition in any particular order, and the sum may not even be representable in the same floating-point type. – tmyklebu Oct 28 '14 at 19:03
  • @tmyklebu that's true, but in this case the exact sum has very few chance to meet the expectation either, 0.33+0.33+0.34 (the doubles) sum ups to 18014398509481985/18014398509481984, that's slightly more than 1. OK, PascalCuoq trick may help, but what if the expected sum is not representable, 31/100 as in my example? – aka.nice Oct 28 '14 at 19:21
  • @tmyklebu I mean the exact sum is allways a fraction with a power of 2 at denominator, that leaves many cents fractions out of the scene. – aka.nice Oct 28 '14 at 19:30
  • OP has dueling expectations of floating-point numbers of dollars adding up both when printed in decimal and when added together as floating-point numbers. This is clearly unreasonable. However, the expected sum *is* always representable since it's passed in as a `double`. – tmyklebu Oct 28 '14 at 20:01