diff options
| author | Nicolas Martinelli <nim@odoo.com> | 2018-01-25 13:06:27 +0100 |
|---|---|---|
| committer | Nicolas Martinelli <nim-odoo@users.noreply.github.com> | 2018-01-29 11:06:33 +0100 |
| commit | 8ed7570b1cc50c55867f75eafb20c1deb89dbf5c (patch) | |
| tree | f310adaf8c3f41a19275457b73f10b2c3998a85c | |
| parent | 43bf9ff29d1897405a575fa8643b7f80b5112c24 (diff) | |
[FIX] tools: float_round DOWN
The result of the following:
```
float_round(2.5, precision_rounding=0.05, rounding_method='DOWN')
```
is 2.45. Instead, 2.5 is expected.
In case of the `DOWN` method, epsilon should be added, not subtracted.
In the meantime, we introduce a slight reorganization of the method to
make it clearer.
opw-805008
| -rw-r--r-- | odoo/addons/base/tests/test_float.py | 18 | ||||
| -rw-r--r-- | odoo/tools/float_utils.py | 27 |
2 files changed, 31 insertions, 14 deletions
diff --git a/odoo/addons/base/tests/test_float.py b/odoo/addons/base/tests/test_float.py index 725ba48365b..6dc1cda0550 100644 --- a/odoo/addons/base/tests/test_float.py +++ b/odoo/addons/base/tests/test_float.py @@ -92,6 +92,18 @@ class TestFloatPrecision(TransactionCase): try_round(1.8, '2', 0, method='UP') try_round(-1.8, '-2', 0, method='UP') + # Try some rounding value with rounding method DOWN instead of HALF-UP + # We use 2.425 because when normalizing 2.425 with precision_digits=3 it gives + # us 2424.9999999999995 as value, and if not handle correctly the rounding DOWN + # value will be incorrect (should be 2.425 and not 2.424) + try_round(2.425, '2.425', method='DOWN') + try_round(2.4249, '2.424', method='DOWN') + try_round(-2.425, '-2.425', method='DOWN') + try_round(-2.4249, '-2.424', method='DOWN') + try_round(-2.500, '-2.500', method='DOWN') + try_round(1.8, '1', 0, method='DOWN') + try_round(-1.8, '-1', 0, method='DOWN') + # Extended float range test, inspired by Cloves Almeida's test on bug #882036. fractions = [.0, .015, .01499, .675, .67499, .4555, .4555, .45555] expecteds = ['.00', '.02', '.01', '.68', '.67', '.46', '.456', '.4556'] @@ -135,8 +147,8 @@ class TestFloatPrecision(TransactionCase): try_compare(-657.4444, -657.445, 1) # Rounding to unusual rounding units (e.g. coin values) - def try_round(amount, expected, precision_rounding=None): - value = float_round(amount, precision_rounding=precision_rounding) + def try_round(amount, expected, precision_rounding=None, method='HALF-UP'): + value = float_round(amount, precision_rounding=precision_rounding, rounding_method=method) result = float_repr(value, precision_digits=2) self.assertEqual(result, expected, 'Rounding error: got %s, expected %s' % (result, expected)) @@ -145,6 +157,8 @@ class TestFloatPrecision(TransactionCase): try_round(457.3, '455.00', precision_rounding=5) try_round(457.5, '460.00', precision_rounding=5) try_round(457.1, '456.00', precision_rounding=3) + try_round(2.5, '2.50', precision_rounding=0.05, method='DOWN') + try_round(-2.5, '-2.50', precision_rounding=0.05, method='DOWN') def test_rounding_04(self): """ check that proper rounding is performed for float persistence """ diff --git a/odoo/tools/float_utils.py b/odoo/tools/float_utils.py index bf3f0463c0a..3e13f9ee0bc 100644 --- a/odoo/tools/float_utils.py +++ b/odoo/tools/float_utils.py @@ -61,9 +61,6 @@ def float_round(value, precision_digits=None, precision_rounding=None, rounding_ # In order to easily support rounding to arbitrary 'steps' (e.g. coin values), # we normalize the value before rounding it as an integer, and de-normalize # after rounding: e.g. float_round(1.3, precision_rounding=.5) == 1.5 - - # TIE-BREAKING: HALF-UP (for normal rounding) - # We want to apply HALF-UP tie-breaking rules, i.e. 0.5 rounds away from 0. # Due to IEE754 float/double representation limits, the approximation of the # real value may be slightly below the tie limit, resulting in an error of # 1 unit in the last place (ulp) after rounding. @@ -74,25 +71,31 @@ def float_round(value, precision_digits=None, precision_rounding=None, rounding_ # Credit: discussion with OpenERP community members on bug 882036 normalized_value = value / rounding_factor # normalize + sign = math.copysign(1.0, normalized_value) epsilon_magnitude = math.log(abs(normalized_value), 2) epsilon = 2**(epsilon_magnitude-53) - if rounding_method == 'HALF-UP': - normalized_value += math.copysign(epsilon, normalized_value) - rounded_value = round(normalized_value) # round to integer - # TIE-BREAKING: UP (for ceiling[resp. flooring] operations) - # When rounding the value up[resp. down], we instead subtract the epsilon value + # TIE-BREAKING: UP/DOWN (for ceiling[resp. flooring] operations) + # When rounding the value up[resp. down], we instead subtract[resp. add] the epsilon value # as the the approximation of the real value may be slightly *above* the # tie limit, this would result in incorrectly rounding up[resp. down] to the next number # The math.ceil[resp. math.floor] operation is applied on the absolute value in order to # round "away from zero" and not "towards infinity", then the sign is # restored. - else: - func = math.floor if rounding_method == 'DOWN' else math.ceil - sign = math.copysign(1.0, normalized_value) + if rounding_method == 'UP': normalized_value -= sign*epsilon - rounded_value = func(abs(normalized_value)) * sign + rounded_value = math.ceil(abs(normalized_value)) * sign + + elif rounding_method == 'DOWN': + normalized_value += sign*epsilon + rounded_value = math.floor(abs(normalized_value)) * sign + + # TIE-BREAKING: HALF-UP (for normal rounding) + # We want to apply HALF-UP tie-breaking rules, i.e. 0.5 rounds away from 0. + else: + normalized_value += math.copysign(epsilon, normalized_value) + rounded_value = round(normalized_value) # round to integer result = rounded_value * rounding_factor # de-normalize return result |
