diff --git a/odoo/addons/base/tests/test_float.py b/odoo/addons/base/tests/test_float.py index db367f5fe0d..d0c07d5460b 100644 --- a/odoo/addons/base/tests/test_float.py +++ b/odoo/addons/base/tests/test_float.py @@ -71,8 +71,10 @@ class TestFloatPrecision(TransactionCase): result = float_repr(value, precision_digits=digits) self.assertEqual(result, expected, 'Rounding error: got %s, expected %s' % (result, expected)) - try_round(2.6745, '2.675') - try_round(-2.6745, '-2.675') + try_round(2.6735, '2.674') # Tie rounds away from 0 + try_round(-2.6735, '-2.674') # Tie rounds away from 0 + try_round(2.6745, '2.675') # Tie rounds away from 0 + try_round(-2.6745, '-2.675') # Tie rounds away from 0 try_round(2.6744, '2.674') try_round(-2.6744, '-2.674') try_round(0.0004, '0.000') @@ -82,6 +84,34 @@ class TestFloatPrecision(TransactionCase): try_round(457.4554, '457.455') try_round(-457.4554, '-457.455') + # Try some rounding value with rounding method HALF-DOWN instead of HALF-UP + try_round(2.6735, '2.673', method='HALF-DOWN') # Tie rounds towards 0 + try_round(-2.6735, '-2.673', method='HALF-DOWN') # Tie rounds towards 0 + try_round(2.6745, '2.674', method='HALF-DOWN') # Tie rounds towards 0 + try_round(-2.6745, '-2.674', method='HALF-DOWN') # Tie rounds towards 0 + try_round(2.6744, '2.674', method='HALF-DOWN') + try_round(-2.6744, '-2.674', method='HALF-DOWN') + try_round(0.0004, '0.000', method='HALF-DOWN') + try_round(-0.0004, '-0.000', method='HALF-DOWN') + try_round(357.4555, '357.455', method='HALF-DOWN') + try_round(-357.4555, '-357.455', method='HALF-DOWN') + try_round(457.4554, '457.455', method='HALF-DOWN') + try_round(-457.4554, '-457.455', method='HALF-DOWN') + + # Try some rounding value with rounding method HALF-EVEN instead of HALF-UP + try_round(2.6735, '2.674', method='HALF-EVEN') # Tie rounds to the closest even number (i.e. up here) + try_round(-2.6735, '-2.674', method='HALF-EVEN') # Tie rounds to the closest even number (i.e. up here) + try_round(2.6745, '2.674', method='HALF-EVEN') # Tie rounds to the closest even number (i.e. down here) + try_round(-2.6745, '-2.674', method='HALF-EVEN') # Tie rounds to the closest even number (i.e. down here) + try_round(2.6744, '2.674', method='HALF-EVEN') + try_round(-2.6744, '-2.674', method='HALF-EVEN') + try_round(0.0004, '0.000', method='HALF-EVEN') + try_round(-0.0004, '-0.000', method='HALF-EVEN') + try_round(357.4555, '357.455', method='HALF-EVEN') + try_round(-357.4555, '-357.455', method='HALF-EVEN') + try_round(457.4554, '457.455', method='HALF-EVEN') + try_round(-457.4554, '-457.455', method='HALF-EVEN') + # Try some rounding value with rounding method UP instead of HALF-UP # We use 8.175 because when normalizing 8.175 with precision_digits=3 it gives # us 8175,0000000001234 as value, and if not handle correctly the rounding UP diff --git a/odoo/tools/float_utils.py b/odoo/tools/float_utils.py index c54dd0b9270..592112ab769 100644 --- a/odoo/tools/float_utils.py +++ b/odoo/tools/float_utils.py @@ -45,10 +45,13 @@ def float_round(value, precision_digits=None, precision_rounding=None, rounding_ :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 rounding_method: the rounding method used: 'HALF-UP', 'UP' or 'DOWN', - the first one rounding up to the closest number with the rule that - number>=0.5 is rounded up to 1, the second always rounding up and the - latest one always rounding down. + :param rounding_method: the rounding method used: + - 'HALF-UP' will round to the closest number with ties going away from zero. + - 'HALF-DOWN' will round to the closest number with ties going towards zero. + - 'HALF_EVEN' will round to the closest number with ties going to the closest + even number. + - 'UP' will always round away from 0. + - 'DOWN' will always round towards 0. :return: rounded float """ rounding_factor = _float_check_precision(precision_digits=precision_digits, @@ -90,6 +93,17 @@ def float_round(value, precision_digits=None, precision_rounding=None, rounding_ normalized_value += sign*epsilon rounded_value = math.floor(abs(normalized_value)) * sign + # TIE-BREAKING: HALF-EVEN + # We want to apply HALF-EVEN tie-breaking rules, i.e. 0.5 rounds towards closest even number. + elif rounding_method == 'HALF-EVEN': + rounded_value = math.copysign(builtins.round(normalized_value), normalized_value) + + # TIE-BREAKING: HALF-DOWN + # We want to apply HALF-DOWN tie-breaking rules, i.e. 0.5 rounds towards 0. + elif rounding_method == 'HALF-DOWN': + normalized_value -= math.copysign(epsilon, normalized_value) + rounded_value = round(normalized_value) + # 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: