[IMP] odoo.tools: add HALF-EVEN and HALF-DOWN to float_round

Add support for `HALF-EVEN` and `HALF-DOWN` as value
for `rounding_method` argument of `float_round()`.

closes odoo/odoo#152227

Signed-off-by: Raphael Collet <rco@odoo.com>
This commit is contained in:
Ruben Gomes (rugo)
2024-02-05 13:48:29 +00:00
parent fda5d5f8d1
commit e538c584ec
2 changed files with 50 additions and 6 deletions
+32 -2
View File
@@ -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
+18 -4
View File
@@ -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: