[IMP] tools.float_*: more tests with negative values, some cleanup
authorOlivier Dony <odo@openerp.com>
Tue, 15 Nov 2011 11:36:05 +0000 (12:36 +0100)
committerOlivier Dony <odo@openerp.com>
Tue, 15 Nov 2011 11:36:05 +0000 (12:36 +0100)
bzr revid: odo@openerp.com-20111115113605-l5e6gg675qbrvjz6

openerp/addons/base/test/base_test.yml
openerp/tools/misc.py

index 9b686cc..56d723b 100644 (file)
         self.pool._init = True
 
 -
-    "Float precision tests: verify that float rounding methods are working correctly"
+    "Float precision tests: verify that float rounding methods are working correctly via res.currency"
 -
     !python {model: res.currency}: |
         currency = self.browse(cr, uid, ref('base.EUR'))
-        def try_round(self, cr, currency, amount, expected):
+        def try_round(amount, expected, self=self, cr=cr, currency=currency):
             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
+        try_round(2.674,'2.67')
+        try_round(2.675,'2.68')   # in Python 2.7.2, round(2.675,2) gives 2.67
+        try_round(-2.675,'-2.68') # in Python 2.7.2, round(2.675,2) gives 2.67
+        try_round(0.001,'0.0')
+        try_round(-0.001,'-0.0')
+        try_round(0.0049,'0.0')   # 0.0049 is closer to 0 than to 0.01, so should round down
+        try_round(0.005,'0.01')   # the rule is to round half away from zero
+        try_round(-0.005,'-0.01') # the rule is to round half away from zero
 
-        def try_zero(self, cr, currency, amount, expected):
+        def try_zero(amount, expected, self=self, cr=cr, currency=currency):
             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
+        try_zero(0.01, False)
+        try_zero(-0.01, False)
+        try_zero(0.001, True)
+        try_zero(-0.001, True)
+        try_zero(0.0046, True)
+        try_zero(-0.0046, True)
+        try_zero(2.68-2.675, False) # 2.68 - 2.675 = 0.005 -> rounds to 0.01
+        try_zero(2.68-2.676, True)  # 2.68 - 2.675 = 0.004 -> rounds to 0.0
+        try_zero(2.676-2.68, True)  # 2.675 - 2.68 = -0.004 -> rounds to -0.0
+        try_zero(2.675-2.68, False) # 2.675 - 2.68 = -0.005 -> rounds to -0.01
 
-        def try_compare(self, cr, currency, amount1, amount2, expected):
+        def try_compare(amount1, amount2, expected, self=self, cr=cr, currency=currency):
             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)
+        try_compare(0.001, 0.001, 0)
+        try_compare(-0.001, -0.001, 0)
+        try_compare(0.001, 0.002, 0)
+        try_compare(-0.001, -0.002, 0)
+        try_compare(2.675, 2.68, 0)
+        try_compare(2.676, 2.68, 0)
+        try_compare(-2.676, -2.68, 0)
+        try_compare(2.674, 2.68, -1)
+        try_compare(-2.674, -2.68, 1)
+        try_compare(3, 2.68, 1)
+        try_compare(-3, -2.68, -1)
+        try_compare(0.01, 0, 1)
+        try_compare(-0.01, 0, -1)
 
+-
+    "Float precision tests: verify that float rounding methods are working correctly via tools"
+-
+    !python {model: res.currency}: |
         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))
+        def try_round(amount, expected, precision_digits=3, float_round=float_round):
+            result = str(float_round(amount, precision_digits=precision_digits))
             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')
+        try_round(2.6745, '2.675')
+        try_round(-2.6745, '-2.675')
+        try_round(2.6744, '2.674')
+        try_round(-2.6744, '-2.674')
+        try_round(0.0004, '0.0')
+        try_round(-0.0004, '-0.0')
+        #try_round(357.4555, '357.456')
+        #try_round(-357.4555, '-357.456')
+        try_round(457.4554, '457.455')
+        try_round(-457.4554, '-457.455')
 
-        def try_zero_digits(float_is_zero, amount, expected):
+
+        def try_zero(amount, expected, float_is_zero=float_is_zero):
             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)
+        try_zero(0.0002, True)
+        try_zero(-0.0002, True)
+        try_zero(0.00034, True)
+        try_zero(0.0005, False)
+        try_zero(-0.0005, False)
+        try_zero(0.0008, False)
+        try_zero(-0.0008, False)
 
-        def try_compare_digits(float_compare, amount1, amount2, expected):
+        def try_compare(amount1, amount2, expected, float_compare=float_compare):
             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)
+        try_compare(0.0003, 0.0004, 0)
+        try_compare(-0.0003, -0.0004, 0)
+        try_compare(0.0002, 0.0005, -1)
+        try_compare(-0.0002, -0.0005, 1)
+        try_compare(0.0009, 0.0004, 1)
+        try_compare(-0.0009, -0.0004, -1)
+        try_compare(557.4555, 557.4556, 0)
+        try_compare(-557.4555, -557.4556, 0)
+        try_compare(657.4444, 657.445, -1)
+        try_compare(-657.4444, -657.445, 1)
+
+        # Rounding to different precision_roundings
+        def try_round(amount, expected, precision_rounding=None, float_round=float_round):
+            result = str(float_round(amount, precision_rounding=precision_rounding))
+            assert result == expected, 'Rounding error: got %s, expected %s' % (result, expected)
+        try_round(-457.4554, '-457.45', precision_rounding=0.05)
+        try_round(457.444, '457.5', precision_rounding=0.5)
+        try_round(457.3, '455.0', precision_rounding=5)
+        try_round(457.5, '460.0', precision_rounding=5)
+        try_round(457.1, '456.0', precision_rounding=3)
 
-        # specifying 2 precisions is illegal:
+-
+    "Float precision tests: verify that invalid parameters are forbidden"
+-
+    !python {model: res.currency}: |
+        from tools import float_compare, float_is_zero, float_round
         try:
             float_is_zero(0.01, precision_digits=3, precision_rounding=0.01)
         except AssertionError:
index 496767f..5b57137 100644 (file)
@@ -1208,9 +1208,9 @@ def _float_check_precision(precision_digits=None, precision_rounding=None):
         return 10 ** -precision_digits
     return precision_rounding
 
-def float_round(amount, precision_digits=None, precision_rounding=None):
-    """Return ``amount`` rounded to ``precision_digits``
-       decimal digits, minimizing IEEE-854 floating point representation
+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!
@@ -1223,7 +1223,7 @@ def float_round(amount, precision_digits=None, precision_rounding=None):
           >>> round(2.675,2)
           2.67
 
-       :param float amount: the amount to round
+       :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 
@@ -1233,18 +1233,18 @@ def float_round(amount, precision_digits=None, precision_rounding=None):
     rounding_factor = _float_check_precision(precision_digits=precision_digits,
                                              precision_rounding=precision_rounding)
     if rounding_factor == 0: return 0.0
-    # /!\ First member below must be rounded to full unit!
-    # Do not pass rounding digits to round()!
-    return round(amount / rounding_factor) * rounding_factor
 
-def float_is_zero(amount, precision_digits=None, precision_rounding=None):
-    """Returns true if ``amount`` is small enough to be treated as
+    # 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(amount1-amount2)`` is not always equivalent to 
-       ``float_compare(amount1,amount2) == 0``, as the former will round after
+       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. 
 
@@ -1252,16 +1252,16 @@ def float_is_zero(amount, precision_digits=None, precision_rounding=None):
        :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 amount: amount to compare with currency's zero
-       :return: True if ``amount`` is considered 0
+       :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(amount, precision_rounding=rounding_factor)) < rounding_factor
+    return abs(float_round(value, precision_rounding=rounding_factor)) < rounding_factor
 
-def float_compare(amount1, amount2, precision_digits=None, precision_rounding=None):
-    """Compare ``amount1`` and ``amount2`` after rounding them according to the
-       given precision. An amount is considered lower/greater than another amount
+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!
 
@@ -1279,16 +1279,16 @@ def float_compare(amount1, amount2, precision_digits=None, precision_rounding=No
        :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 amount1: first amount to compare
-       :param float amount2: second amount to compare
-       :return: (resp.) -1, 0 or 1, if ``amount1`` is (resp.) lower than,
-           equal to, or greater than ``amount2``, at the given 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)
-    amount1 = float_round(amount1, precision_rounding=rounding_factor)
-    amount2 = float_round(amount2, precision_rounding=rounding_factor)
-    delta = amount1 - amount2
+    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