Question about floating point

Steven D'Aprano steve+comp.lang.python at pearwood.info
Wed Aug 29 20:32:18 EDT 2018


On Tue, 28 Aug 2018 16:47:25 +0200, Frank Millman wrote:

> The reason for my query is this. I am assisting someone with an
> application involving monetary values. I have been trying to explain why
> they should use the decimal module. They have had a counter-argument
> from someone else who says they should just use the rounding technique
> in my third example above.

*head-desk*

And this is why we can't have nice things.

Presumably this money application doesn't just work on hard-coded literal 
values, right? So this "programmer" your friend is listening to prefers 
to write this:

    money = (a + b)*10/10

instead of:

    money = a + b

presumably because programming isn't hard enough without superstitious 
ritual that doesn't actually solve the problem.

In the second case, you have (potentially) *one* rounding error, due to 
the addition.

In the first case, you get the *exact same rounding error* when you do 
(a+b). Then you get a second rounding error by multiplying by ten, and a 
third rounding error when you divide by ten.

Now its true that sometimes those rounding errors will cancel. You found 
an example:

py> (1.1 + 2.2)*10/10 == 3.3
True

but it took me four attempts to find a counter-example, where the errors 
don't cancel:

py> (49675.23 + 10492.95)*10/10 == 60168.18
False

To prove it isn't a fluke:

py> (731984.84 + 173.32)*10/10 == 732158.16
False

py> (170734.84 - 173.39)*10/10 == 170561.45
False


Given that it has three possible three rounding errors instead of one, it 
is even possible that this "clever trick" could end up being *worse* than 
just doing a single addition. But my care factor isn't high enough to 
track down an example (if one exists).

For nearly all applications involving money, one correct solution is to 
use either integer numbers of cents (or whatever the smallest currency 
you ever care about happens to be). Then all additions, subtractions and 
multiplications will be exact, without fail, and you only need to worry 
about rounding divisions. You can minimize (but not eliminate) that by 
calculating in tenths of a cent, which effectively gives you a guard 
digit.

Or, just use decimal, which is *designed* for monetary applications 
(among other things). You decide on how many decimal places to keep (say, 
two, or three if you want a guard digit), a rounding mode (Banker's 
Rounding is recommended for financial applications), and just do your 
calculations with no "clever tricks".

Add two numbers, then add tax:

    money = (a+b)*(1+t/100)

compared to the "clever trick":

    money = (a+b)*10/10 * (1 + t)*10/10


Which would you rather do?



-- 
Steven D'Aprano
"Ever since I learned about confirmation bias, I've been seeing
it everywhere." -- Jon Ronson




More information about the Python-list mailing list