[IMP] tools.float_*: added tests and docstrings
bzr revid: odo@openerp.com-20111114182310-kkzh78uej0uuwhaz
This commit is contained in:
parent
dcb5ff6eac
commit
ffe640cece
|
@ -128,8 +128,7 @@ class res_currency(osv.osv):
|
|||
|
||||
def round(self, cr, uid, currency, amount):
|
||||
"""Return ``amount`` rounded according to ``currency``'s
|
||||
rounding rules, also minimizing IEEE-754 floating point
|
||||
representation errors.
|
||||
rounding rules.
|
||||
|
||||
:param browse_record currency: currency for which we are rounding
|
||||
:param float amount: the amount to round
|
||||
|
@ -138,12 +137,16 @@ class res_currency(osv.osv):
|
|||
return float_round(amount, precision_rounding=currency.rounding)
|
||||
|
||||
def compare_amounts(self, cr, uid, currency, amount1, amount2):
|
||||
"""Compare ``amount1`` and ``amount2`` according to ``currency``'s
|
||||
rounding rules, and return (resp.) -1, 0 or 1, if ``amount1``
|
||||
is (resp.) lower than, equal to, or greater than ``amount2``.
|
||||
"""Compare ``amount1`` and ``amount2`` after rounding them according to the
|
||||
given currency's precision..
|
||||
An amount is considered lower/greater than another amount if their rounded
|
||||
value is different. This is not the same as having a non-zero difference!
|
||||
|
||||
For example 1.432 and 1.431 are equal if currency is rounded to
|
||||
2 digits, so this method would return 0
|
||||
For example 1.432 and 1.431 are equal at 2 digits precision,
|
||||
so this method would return 0.
|
||||
However 0.006 and 0.002 are considered different (returns 1) because
|
||||
they respectively round to 0.01 and 0.0, even though
|
||||
0.006-0.002 = 0.004 which would be considered zero at 2 digits precision.
|
||||
|
||||
:param browse_record currency: currency for which we are rounding
|
||||
:param float amount1: first amount to compare
|
||||
|
@ -157,6 +160,12 @@ class res_currency(osv.osv):
|
|||
def is_zero(self, cr, uid, currency, amount):
|
||||
"""Returns true if ``amount`` is small enough to be treated as
|
||||
zero according to ``currency``'s rounding rules.
|
||||
|
||||
Warning: ``is_zero(amount1-amount2)`` is not always equivalent to
|
||||
``compare_amounts(amount1,amount2) == 0``, as the former will round after
|
||||
computing the difference, while the latter will round before, giving
|
||||
different results for e.g. 0.006 and 0.002 at 2 digits precision.
|
||||
|
||||
:param browse_record currency: currency for which we are rounding
|
||||
:param float amount: amount to compare with currency's zero
|
||||
"""
|
||||
|
|
|
@ -144,3 +144,71 @@
|
|||
!python {model: res.partner.category}: |
|
||||
self.pool._init = True
|
||||
|
||||
-
|
||||
"Float precision tests: verify that float rounding methods are working correctly"
|
||||
-
|
||||
!python {model: res.currency}: |
|
||||
currency = self.browse(cr, uid, ref('base.EUR'))
|
||||
def try_round(self, cr, currency, amount, expected):
|
||||
result = str(self.round(cr, 1, currency, amount))
|
||||
assert result == expected, 'Rounding error: got %s, expected %s' % (result, expected)
|
||||
try_round(self, cr, currency, 2.674,'2.67')
|
||||
try_round(self, cr, currency, 2.675,'2.68') # in Python 2.7.2, round(2.675,2) gives 2.67
|
||||
try_round(self, cr, currency, 0.001,'0.0')
|
||||
try_round(self, cr, currency, 0.0049,'0.0') # 0.0049 is closer to 0 than to 0.01, so should round down
|
||||
try_round(self, cr, currency, 0.005,'0.01') # the rule is to round half up
|
||||
|
||||
def try_zero(self, cr, currency, amount, expected):
|
||||
assert self.is_zero(cr, 1, currency, amount) == expected, "Rounding error: %s should be zero!" % amount
|
||||
try_zero(self, cr, currency, 0.01, False)
|
||||
try_zero(self, cr, currency, 0.001, True)
|
||||
try_zero(self, cr, currency, 0.0046, True)
|
||||
try_zero(self, cr, currency, 2.68-2.675, False) # 2.68 - 2.675 = 0.005 -> rounds to 0.01
|
||||
try_zero(self, cr, currency, 2.68-2.676, True) # 2.68 - 2.675 = 0.004 -> rounds to 0.0
|
||||
|
||||
def try_compare(self, cr, currency, amount1, amount2, expected):
|
||||
assert self.compare_amounts(cr, 1, currency, amount1, amount2) == expected, \
|
||||
"Rounding error, compare_amounts(%s,%s) should be %s" % (amount1, amount2, expected)
|
||||
try_compare(self, cr, currency, 0.001, 0.001, 0)
|
||||
try_compare(self, cr, currency, 0.001, 0.002, 0)
|
||||
try_compare(self, cr, currency, 2.675, 2.68, 0)
|
||||
try_compare(self, cr, currency, 2.676, 2.68, 0)
|
||||
try_compare(self, cr, currency, 2.674, 2.68, -1)
|
||||
try_compare(self, cr, currency, 3, 2.68, 1)
|
||||
try_compare(self, cr, currency, 0.01, 0, 1)
|
||||
|
||||
from tools import float_compare, float_is_zero, float_round
|
||||
def try_round_digits(float_round, amount, expected):
|
||||
result = str(float_round(amount, precision_digits=3))
|
||||
assert result == expected, 'Rounding error: got %s, expected %s' % (result, expected)
|
||||
try_round_digits(float_round, 2.6745, '2.675')
|
||||
try_round_digits(float_round, 2.6744, '2.674')
|
||||
try_round_digits(float_round, 0.0004, '0.0')
|
||||
|
||||
def try_zero_digits(float_is_zero, amount, expected):
|
||||
assert float_is_zero(amount, precision_digits=3) == expected, "Rounding error: %s should be zero!" % amount
|
||||
try_zero_digits(float_is_zero, 0.0002, True)
|
||||
try_zero_digits(float_is_zero, 0.00034, True)
|
||||
try_zero_digits(float_is_zero, 0.0005, False)
|
||||
try_zero_digits(float_is_zero, 0.0008, False)
|
||||
|
||||
def try_compare_digits(float_compare, amount1, amount2, expected):
|
||||
assert float_compare(amount1, amount2, precision_digits=3) == expected, \
|
||||
"Rounding error, compare_amounts(%s,%s) should be %s" % (amount1, amount2, expected)
|
||||
try_compare_digits(float_compare, 0.0003, 0.0004, 0)
|
||||
try_compare_digits(float_compare, 0.0002, 0.0005, -1)
|
||||
try_compare_digits(float_compare, 0.0009, 0.0004, 1)
|
||||
|
||||
# specifying 2 precisions is illegal:
|
||||
try:
|
||||
float_is_zero(0.01, precision_digits=3, precision_rounding=0.01)
|
||||
except AssertionError:
|
||||
pass
|
||||
try:
|
||||
float_compare(0.01, 0.02, precision_digits=3, precision_rounding=0.01)
|
||||
except AssertionError:
|
||||
pass
|
||||
try:
|
||||
float_round(0.01, precision_digits=3, precision_rounding=0.01)
|
||||
except AssertionError:
|
||||
pass
|
||||
|
|
|
@ -1210,11 +1210,13 @@ def _float_check_precision(precision_digits=None, precision_rounding=None):
|
|||
|
||||
def float_round(amount, precision_digits=None, precision_rounding=None):
|
||||
"""Return ``amount`` rounded to ``precision_digits``
|
||||
decimal digits, minimizing IEEE-754 floating point representation
|
||||
decimal digits, minimizing IEEE-854 floating point representation
|
||||
errors.
|
||||
Precision must be given by ``precision_digits`` or ``precision_rounding``,
|
||||
not both!
|
||||
Example on Python 2.7.2::
|
||||
|
||||
To illustrate how this is different from the default round() builtin,
|
||||
here is an example (depends on Python version, here is for v2.7.2 x64)::
|
||||
|
||||
>>> round_float(2.675)
|
||||
2.68
|
||||
|
@ -1241,6 +1243,11 @@ def float_is_zero(amount, precision_digits=None, precision_rounding=None):
|
|||
Precision must be given by ``precision_digits`` or ``precision_rounding``,
|
||||
not both!
|
||||
|
||||
Warning: ``float_is_zero(amount1-amount2)`` is not always equivalent to
|
||||
``float_compare(amount1,amount2) == 0``, as the former will round after
|
||||
computing the difference, while the latter will round before, giving
|
||||
different results for e.g. 0.006 and 0.002 at 2 digits precision.
|
||||
|
||||
:param int precision_digits: number of decimal digits to round to.
|
||||
:param float precision_rounding: decimal number representing the minimum
|
||||
non-zero value at the desired precision (for example, 0.01 for a
|
||||
|
@ -1253,13 +1260,20 @@ def float_is_zero(amount, precision_digits=None, precision_rounding=None):
|
|||
return abs(float_round(amount, precision_rounding=rounding_factor)) < rounding_factor
|
||||
|
||||
def float_compare(amount1, amount2, precision_digits=None, precision_rounding=None):
|
||||
"""Compare ``amount1`` and ``amount2`` according
|
||||
to the given precision.
|
||||
Precision must be given by ``precision_digits`` or ``precision_rounding``,
|
||||
not both!
|
||||
"""Compare ``amount1`` and ``amount2`` after rounding them according to the
|
||||
given precision. An amount is considered lower/greater than another amount
|
||||
if their rounded value is different. This is not the same as having a
|
||||
non-zero difference!
|
||||
|
||||
For example 1.432 and 1.431 are equal at 2 digits precision,
|
||||
so this method would return 0
|
||||
However 0.006 and 0.002 are considered different (returns 1) because
|
||||
they respectively round to 0.01 and 0.0, even though
|
||||
0.006-0.002 = 0.004 which would be considered zero at 2 digits precision.
|
||||
|
||||
|
||||
Precision must be given by ``precision_digits`` or ``precision_rounding``,
|
||||
not both!
|
||||
|
||||
:param int precision_digits: number of decimal digits to round to.
|
||||
:param float precision_rounding: decimal number representing the minimum
|
||||
|
@ -1272,9 +1286,10 @@ def float_compare(amount1, amount2, precision_digits=None, precision_rounding=No
|
|||
"""
|
||||
rounding_factor = _float_check_precision(precision_digits=precision_digits,
|
||||
precision_rounding=precision_rounding)
|
||||
amount1 = float_round(amount1, precision_rounding=rounding_factor)
|
||||
amount2 = float_round(amount2, precision_rounding=rounding_factor)
|
||||
delta = amount1 - amount2
|
||||
if float_is_zero(delta, precision_rounding=rounding_factor): return 0
|
||||
delta = float_round(delta, precision_rounding=rounding_factor)
|
||||
return -1 if delta < 0 else 1
|
||||
return -1 if delta < 0.0 else 1
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
Loading…
Reference in New Issue