diff --git a/openerp/addons/base/res/res_currency.py b/openerp/addons/base/res/res_currency.py index c67c7d65260..098b0179d57 100644 --- a/openerp/addons/base/res/res_currency.py +++ b/openerp/addons/base/res/res_currency.py @@ -24,7 +24,7 @@ import netsvc from osv import fields, osv import tools -from tools.misc import currency, float_round, float_is_zero, float_compare +from tools import float_round, float_is_zero, float_compare from tools.translate import _ CURRENCY_DISPLAY_PATTERN = re.compile(r'(\w+)\s*(?:\((.*)\))?') diff --git a/openerp/tools/__init__.py b/openerp/tools/__init__.py index 3d1cdc960ab..6875ebd78fc 100644 --- a/openerp/tools/__init__.py +++ b/openerp/tools/__init__.py @@ -31,6 +31,7 @@ from amount_to_text_en import * from pdf_utils import * from yaml_import import * from sql import * +from float_utils import * #.apidoc title: Tools diff --git a/openerp/tools/float_utils.py b/openerp/tools/float_utils.py new file mode 100644 index 00000000000..dde6aa5962c --- /dev/null +++ b/openerp/tools/float_utils.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Business Applications +# Copyright (c) 2011 OpenERP S.A. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +def _float_check_precision(precision_digits=None, precision_rounding=None): + assert (precision_digits is not None or precision_rounding is not None) and \ + not (precision_digits and precision_rounding),\ + "exactly one of precision_digits and precision_rounding must be specified" + if precision_digits is not None: + return 10 ** -precision_digits + return precision_rounding + +def float_round(value, precision_digits=None, precision_rounding=None): + """Return ``value`` rounded to ``precision_digits`` + decimal digits, minimizing IEEE-754 floating point representation + errors. + Precision must be given by ``precision_digits`` or ``precision_rounding``, + not both! + + 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 + >>> round(2.675,2) + 2.67 + + :param float value: the value to round + :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 + 2-digit precision). + :return: rounded float + """ + rounding_factor = _float_check_precision(precision_digits=precision_digits, + precision_rounding=precision_rounding) + if rounding_factor == 0: return 0.0 + + # Then round to integer wrt. rounding factor + return round(value / rounding_factor) * rounding_factor + +def float_is_zero(value, precision_digits=None, precision_rounding=None): + """Returns true if ``value`` is small enough to be treated as + zero at the given precision. + Precision must be given by ``precision_digits`` or ``precision_rounding``, + not both! + + Warning: ``float_is_zero(value1-value2)`` is not always equivalent to + ``float_compare(value1,value2) == 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 + 2-digit precision). + :param float value: value to compare with currency's zero + :return: True if ``value`` is considered 0 + """ + rounding_factor = _float_check_precision(precision_digits=precision_digits, + precision_rounding=precision_rounding) + return abs(float_round(value, precision_rounding=rounding_factor)) < rounding_factor + +def float_compare(value1, value2, precision_digits=None, precision_rounding=None): + """Compare ``value1`` and ``value2`` after rounding them according to the + given precision. A value is considered lower/greater than another value + 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 + non-zero value at the desired precision (for example, 0.01 for a + 2-digit precision). + :param float value1: first value to compare + :param float value2: second value to compare + :return: (resp.) -1, 0 or 1, if ``value1`` is (resp.) lower than, + equal to, or greater than ``value2``, at the given precision. + """ + rounding_factor = _float_check_precision(precision_digits=precision_digits, + precision_rounding=precision_rounding) + value1 = float_round(value1, precision_rounding=rounding_factor) + value2 = float_round(value2, precision_rounding=rounding_factor) + delta = value1 - value2 + if float_is_zero(delta, precision_rounding=rounding_factor): return 0 + return -1 if delta < 0.0 else 1 \ No newline at end of file diff --git a/openerp/tools/misc.py b/openerp/tools/misc.py index 5b5713749ac..7fdb2f07386 100644 --- a/openerp/tools/misc.py +++ b/openerp/tools/misc.py @@ -1200,96 +1200,4 @@ class UnquoteEvalContext(defaultdict): def __missing__(self, key): return unquote(key) -def _float_check_precision(precision_digits=None, precision_rounding=None): - assert (precision_digits is not None or precision_rounding is not None) and \ - not (precision_digits and precision_rounding),\ - "exactly one of precision_digits and precision_rounding must be specified" - if precision_digits is not None: - return 10 ** -precision_digits - return precision_rounding - -def float_round(value, precision_digits=None, precision_rounding=None): - """Return ``value`` rounded to ``precision_digits`` - decimal digits, minimizing IEEE-754 floating point representation - errors. - Precision must be given by ``precision_digits`` or ``precision_rounding``, - not both! - - 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 - >>> round(2.675,2) - 2.67 - - :param float value: the value to round - :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 - 2-digit precision). - :return: rounded float - """ - rounding_factor = _float_check_precision(precision_digits=precision_digits, - precision_rounding=precision_rounding) - if rounding_factor == 0: return 0.0 - - # Then round to integer wrt. rounding factor - return round(value / rounding_factor) * rounding_factor - -def float_is_zero(value, precision_digits=None, precision_rounding=None): - """Returns true if ``value`` is small enough to be treated as - zero at the given precision. - Precision must be given by ``precision_digits`` or ``precision_rounding``, - not both! - - Warning: ``float_is_zero(value1-value2)`` is not always equivalent to - ``float_compare(value1,value2) == 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 - 2-digit precision). - :param float value: value to compare with currency's zero - :return: True if ``value`` is considered 0 - """ - rounding_factor = _float_check_precision(precision_digits=precision_digits, - precision_rounding=precision_rounding) - return abs(float_round(value, precision_rounding=rounding_factor)) < rounding_factor - -def float_compare(value1, value2, precision_digits=None, precision_rounding=None): - """Compare ``value1`` and ``value2`` after rounding them according to the - given precision. A value is considered lower/greater than another value - 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 - non-zero value at the desired precision (for example, 0.01 for a - 2-digit precision). - :param float value1: first value to compare - :param float value2: second value to compare - :return: (resp.) -1, 0 or 1, if ``value1`` is (resp.) lower than, - equal to, or greater than ``value2``, at the given precision. - """ - rounding_factor = _float_check_precision(precision_digits=precision_digits, - precision_rounding=precision_rounding) - value1 = float_round(value1, precision_rounding=rounding_factor) - value2 = float_round(value2, precision_rounding=rounding_factor) - delta = value1 - value2 - if float_is_zero(delta, precision_rounding=rounding_factor): return 0 - return -1 if delta < 0.0 else 1 - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file