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 /odoo/tools | |
| 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
Diffstat (limited to 'odoo/tools')
| -rw-r--r-- | odoo/tools/float_utils.py | 27 |
1 files changed, 15 insertions, 12 deletions
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 |
