Comment 36 for bug 882036

Revision history for this message
Raphaël Valyi - http://www.akretion.com (rvalyi) wrote : Re: [Bug 882036] Re: rounding error

Hello,

First I should say I agree with what Fabien said.
Rephrasing quickly the problem: numbers like 0.1 don't have an exact binary
representation and is instead represented
by 0.100000000000000005551115123125 in memory (read
http://docs.python.org/tutorial/floatingpoint.html ) . Hence when you enter
some exact decimals values in a float system such as OpenERP, you introduce
at worst a 2^⁻52 error.
Every time you sum or substract two floats, you add up thoses errors
(French readers can read for instance
http://www-fourier.ujf-grenoble.fr/~parisse/mat249/mat249/node9.html )

Initially float offers a decimal precision of 17 decimal significant digits.
If you have a number like 1 billion with 2 decimal digits (a very
successful company able to fund any further fix in OpenERP), you need a
precision of 9+2 = 11 digits

The whole reasoning is that even propagating an error of 2^-52 or 10^-17
over and over, it will very hardly have any impact at 10^-11 significant
digits (and count most SMB's need a lot less than 11 significant digits).

So by rounding you will filter away that epsilon artifact and go back to
the exact decimal value.

Is that so simple?

Not exactly. Actually (and this is what we talked about in that bug
report), you can still make errors during the rounding operation.
Indeed, it can happen that that little epsilon float artifact will move
your result a little higher or a little below the 5 half number determining
if you should round up or down.

A very simple illustration is:
A =1.0-(1.0-0.001/2)+0.1 = 0.10049999999999995
B = 1.0+0.1-(1.0-0.001/2) = 0.10050000000000003
Using decimals numbers A and B are strictly equal to 0.1005
But with floats, depending the way you compute it, you will end up
at 0.1005 + epsilon or 0.1005 - epsilon

The issue is that if you round blindly, like using round(x, 2)
you get:
round(A, 3) = 0.100
round(B, 3) = 0.101

If you store that in your accounting and sum it over and over (like you
have an invoice with several lines of such A value), you will multiply that
rounding error and get in bad shape.
Taken the float precautions Fabien told, it's hardly something illegal (you
could have an exact product price little different than what displayed at 2
or 3 digits that could have lead o such float result)
But it's just that it at least awkward:
you customer or supplier will compute the invoice with some hand calculator
with a Decimal system or with it's own Decimal based ERP or Decimal based
ecommerce or Decimal based point of sale and will find some result
different from several cents from your OpenERP.
If he pays what he computes on his side, you will end up with a write-off
possibly or you will need to call him to agree on the exact value before...
With an ecommerce the Decimal based order would have a different value than
the invoice from OpenERP...
Things you wouldn't expect and that will give you operational headhaches.

Now, what are we proposing to fix this?
Cloves, Pieter and me are proposing to deliberately introduce some epsilon
in the rounding function to deterministically move out from that x.0005
half danger zone.
As Fabien said, given accounting exact decimal entries, it's impossible
that a number such as 0.14499999999999999 is an exact decimal accounting
number.
Instead, we can assume it's a float artifact and that the real result is 0.145
indeed.
Now, if it was 0.145 may be your rounding policy would require you round it
up. While python default float rounding would naturally round
0.14499999999999999
down (because < 0.145)
The strategy, would then be to deliberately add or substract some very
small epsilon to move out from that danger zone and get he rounding goes
the same side a decimal system would do.

Is that clear to anybody?
Now, does somebody think this strategy is flawed?
Again, naysayers would say, hey if you then really have an exact decimal
number such as 0.14499999999999999 (if this is not a float artifact) if you
add espilon you will make it round up while it should round down. Well may
be industries like banking can have/generate such exact decimal values, but
the idea is that we can assume this will never happen with SMB's.
Are we making a mistake with this assumption? I don't think but I'm open to
debate.

Now, as we talked, determining this little epsilon is not that easy. It
will depend on the number of significant digits of the number you are
rounding. If that number has many significant digits like 1.00499, an
epsilon too large like 10^-4 will destroy our number and make rounding fail.
Now, if instead your float doesn't have many significant digits, such
as -2.675,
an epsilon too small such as 2^-52 will be absorb in the float artifact and
will fail to move us out of the danger zone.

Recently Cloves Almeida made this extensive test float vs decimals:
http://pastebin.com/5dX6t3pR
he tried with an epsilon of 2^-30 and got no error will he is testing a
specially dangerous case of interest rates errors adding over and over.

Finally Cloves proposed an epsilon of 10^(log10(f) - 15) where f is the
float to round.

our rounding function could hence become:

def roundf(f):
    epsilon = 10^(log10(f) - 15)
    return round(f + cmp(f,0)*epsilon,2)

I'm not sure if that is correct. I encourage people to test and tell us
what they think. We could also use Decimal at the very rounding stage, but
we would still need that epsilon strategy to move out of the danger zone
otherwise we are already below or upper than 5 before entering the Decimal
world and it's already too late.

What do you think?

On Thu, Nov 24, 2011 at 11:15 AM, Jacques-Etienne Baudoux (OpenERP) <
<email address hidden>> wrote:

> Fabien,
> I wonder if the error I reported is not also in the clients like I said in
> the bug report. If the product price has a precision of 2 digits (to keep
> initial example in the issue reported) and I enter 1.445 in the GTK or Web
> client (browser) and I go to next field without saving the product, it's
> converted to 1.44 instead of 1.45 without any call to the OpenERP server.
> kr
>
> --
> You received this bug notification because you are a member of OpenERP
> Committers, which is subscribed to OpenERP Server.
> https://bugs.launchpad.net/bugs/882036
>
> Title:
> rounding error
>
> Status in OpenERP Server:
> New
>
> Bug description:
> Concerns 6.0 and trunk.
> If you define a precision of 0.01, the rounding of 0.125 must be 0.13 and
> not 0.12. The error is in the call of the format string "%.2f"%val which
> introduces a mathematical error. The round function must be called to apply
> the correct rounding before formatting the string. It should be:
> "%.2f"%round(val*100)/100
> Fix class digits_change of class float in the server. BUT fix also the
> gtk client AND web client as they all have that error (I let you find the
> right line)
>
> To manage notifications about this bug go to:
> https://bugs.launchpad.net/openobject-server/+bug/882036/+subscriptions
>