← Back to team overview

openerp-expert-accounting team mailing list archive

Re: Floating point precisions, balanced entries and VAT included ref

 

Hello,


> I think your previous statement is not true in several ways...
> 
> * Assuming the accounting precision is 2 and since in OpenERP's
>   accounting modules most fields of type float have as option digits=(16,2)
>   Naively one would expect to be able to use number from
>   99,999,999,999,999.99 to -99,999,999,999,999.99 this is simply not
>   true in the current implementation, although in the database they are
>   store as numeric(16,2) aka Decimal representation and would work as
>   expected, the orm maps this numbers to python floats (= machine-level
>   double precision floating point numbers) in 32 bits (C python) this
>   means 64 bit floating point doubles, which when getting near the limits
>   just can not represent all this numbers, try this in python:
>   >>> 99999999999999.05 == 99999999999999.04
>   True

You are right:
To be precise, the smallest amount if we work in cent of EUR is a number
 bigger than 2,814,749,767,106.00 (and your example is bigger than this):

It's: 2 ** 53 / 2 (because of the sign) / 16 (for the decimal/binary
conversion) / 100 (because we work in cents)

But I don't think it exists companies having such a revenue ?

You are also right that we can not do comparisons on floats, like:
float==float. OpenERP uses the currency object to do this:
self.pool.get('res.currency').is_zero(cr, uid, currency_id, a-b)

But this does not seems to be a big problem as I counted only 4
currencies comparisons in all addons modules of OpenERP.

Let's compare the code in decimal and in float:

In float:

  move = self.browse(cr, uid, move_id, ctx):
  if move.currency_id.is_zero(move.credit - move.debit):
    pass

In decimal:

  move = self.browse(cr, uid, move_id, ctx):
  decimal.getcontext().prec = move.currency_id.digits
  if move.credit==move.debit:
    pass

In float, if you miss the call to is_zero, you have a bug. In decimal,
if you miss the call to getcontext(), you may have a bug too. (depending
if you have operations that changes the precision, like *, /)

So it does not seems to very different. But if you just read and compute
the data (which is much more used than currencies comparison), you have
to call the decimal.getcontext().prec=X method before each operation.
(and take care of side effects if one of the sub-function also change
the context for others operation)

With current float implementation:
  iline = self.browse(cr, uid id, ctx):
  total = iline.qty * iline.price
  self.write(cr, uid, {'total': total})

With decimal:
  iline = self.browse(cr, uid id, ctx):
  decimal.getcontext().prec = iline.invoice_id.currency_id.digits
  total = iline.qty * iline.price
  self.write(cr, uid, {'total': total})

So,
* With decimal: every mathematic operation needs to set the context
before the operation. This context is global per thread, take care if
you mix operations like weight and prices.
Most of the methods uses mathematic operations and this requires to
change most methods of all modules of OpenERP
* With float: only the comparison operator need to call a method on the
currency object. Only a few methods need to compare currencies.

> * XMLRPC does not support decimal, and crash if you try to send/receive
>> decimals. It means most of your methods can not return a dictionary
>> containing decimal objects.
>> It's the same for most standard web-services like SOAP.
> You have a point here, a solution/alternative should be investigated
> further
>> * Decimal can not manage the rounding we need (example in CH: 0.05 FR),
>>   so we need to call rounding methods on currency object to round
>>   prices. We can not expect the decimal module to do it for us.
> 
> 	don't get it how Decimal is different from float here...

Because we delegate the comparison/rounding to the currency object.

>> I already changed the trunk to support float fields having dynamic
>> digits. (using numeric in postgresql and doing a decimal conversion
>> before writing to pgsql).
> 
> still careful should be taken with floats

The database does not use float, but numeric (which is like decimal in
python)

In my conclusion, both methods have their PROs and CONs. I don't like
float but I don't like decimal too.

So, unless we have a better approach, I think it's safer to stay with
the mix: decimal in postgresql, and float in Python code. The main two
reasons:
  - it's a critical change that requires a review of all modules
  - decimal is not accepted by web-services like XML-RPC.


One solution could be to develop a custom class (that inherits or not
from decimal) but which does not requires to set the context before
operations. (and uses the ERP logic, like currencies objects) and which
is casted to string before passing through XML-RPC.


PS: I did not investigated the trouble in the GTK client. Do you know if
decimal is a solution for the spinbutton widget. Did someone tested this ?

-- 
Fabien Pinckaers
CEO Tiny - OpenERP Editor
Chaussée de Namur 40
B-1367 Grand-Rosière
Belgium
Phone: +32.81.81.37.00
Fax: +32.81.73.35.01
Web: http://openerp.com

Great Achievements Start With Tiny Investments
  -- Marty, 2005



References