Saturday, April 28, 2012

C++: rounding a double to a fixed number of decimals

An accounting program handles money amounts, so it is important to avoid computing errors.

The problem is that, using a double, most values cannot be stored exactly but there is a small error. Numbers are stored in base 2, and math tells us that only the decimal numbers obtained dividing some integer value by a power of two will have an exact representation in that base.
So 2.5, 5.25, 3.125 and similar numbers will be stored exactly, while all other number will be represented by a periodic number.
What happens is similar to representing 1/3 in base 10: the result is 0.333333333... with an infinite number of decimals.

A double value has a finite size so it cannot contain infinite decimals: the result is a small error.
There other ways to store decimal values exactly, using a custom class, but I prefer to use a standard type so I use doubles.
Doubles have a precision of about 15 digits, so there is plenty of room to handle money amounts. Rounding errors will be much smaller than the printed values (we will print few decimals) so there should be no problems if computations are handled correctly.

This is true, but I have found a situation where rounding errors lead to a wrong result. I was testing the program and it computed the VAT for an invoice: the right value was 3.16 but the program printed 3.15.

A quick investigation showed that the exact value was 3.155: rounding to two decimals leads to 3.16.
The debuggers showed me that the approximated value contained in the double variable was 3.1549999999999998. The error is very small, but is is enough to cause rounding to 3.15.

I made some quick searches but I did not find anything about this problem, so I had to find a solution by myself.
In the end I decided to modify the function that I use to round to a certain number of decimals: now, before rounding, I add a very small amount (say 0.00000000001) to the number that will be rounded. The value is very small but it is enough to make the number (in the previous example) to become a little more than 3.155 so now rounding is correct.

I don't know if there are better solution to this problem, but I think that adding such a small value to a number that represents a money amount is not likely to introduce errors (at least I have not found any), and it solves the problem.