19.0 vanilla

This commit is contained in:
Ernad Husremovic 2025-10-03 18:07:25 +02:00
parent 0a7ae8db93
commit 991d2234ca
416 changed files with 646602 additions and 300844 deletions

View file

@ -1,8 +1,12 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from typing import Literal, overload
import builtins
import math
RoundingMethod = Literal['UP', 'DOWN', 'HALF-UP', 'HALF-DOWN', 'HALF-EVEN']
__all__ = [
"float_compare",
"float_is_zero",
@ -13,7 +17,7 @@ __all__ = [
]
def round(f):
def round(f: float) -> float:
# P3's builtin round differs from P2 in the following manner:
# * it rounds half to even rather than up (away from 0)
# * round(-0.) loses the sign (it returns -0 rather than 0)
@ -30,7 +34,10 @@ def round(f):
return math.copysign(roundf, f)
def _float_check_precision(precision_digits=None, precision_rounding=None):
def _float_check_precision(
precision_digits: int | None = None,
precision_rounding: float | None = None,
) -> float:
if precision_rounding is not None and precision_digits is None:
assert precision_rounding > 0,\
f"precision_rounding must be positive, got {precision_rounding}"
@ -45,7 +52,28 @@ def _float_check_precision(precision_digits=None, precision_rounding=None):
return precision_rounding
def float_round(value, precision_digits=None, precision_rounding=None, rounding_method='HALF-UP'):
@overload
def float_round(
value: float,
precision_digits: int,
rounding_method: RoundingMethod = ...,
) -> float: ...
@overload
def float_round(
value: float,
precision_rounding: float,
rounding_method: RoundingMethod = ...,
) -> float: ...
def float_round(
value: float,
precision_digits: int | None = None,
precision_rounding: float | None = None,
rounding_method: RoundingMethod = 'HALF-UP',
) -> float:
"""Return ``value`` rounded to ``precision_digits`` decimal digits,
minimizing IEEE-754 floating point representation errors, and applying
the tie-breaking rule selected with ``rounding_method``, by default
@ -53,15 +81,15 @@ def float_round(value, precision_digits=None, precision_rounding=None, rounding_
Precision must be given by ``precision_digits`` or ``precision_rounding``,
not both!
:param float value: the value to round
:param int precision_digits: number of fractional digits to round to.
:param float precision_rounding: decimal number representing the minimum
:param value: the value to round
:param precision_digits: number of fractional digits to round to.
:param 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' 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
- '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.
@ -124,7 +152,25 @@ def float_round(value, precision_digits=None, precision_rounding=None, rounding_
return denormalize(result)
def float_is_zero(value, precision_digits=None, precision_rounding=None):
@overload
def float_is_zero(
value: float,
precision_digits: int,
) -> bool: ...
@overload
def float_is_zero(
value: float,
precision_rounding: float,
) -> bool: ...
def float_is_zero(
value: float,
precision_digits: int | None = None,
precision_rounding: float | None = None,
) -> bool:
"""Returns true if ``value`` is small enough to be treated as
zero at the given precision (smaller than the corresponding *epsilon*).
The precision (``10**-precision_digits`` or ``precision_rounding``)
@ -138,11 +184,11 @@ def float_is_zero(value, precision_digits=None, precision_rounding=None):
computing the difference, while the latter will round before, giving
different results for e.g. 0.006 and 0.002 at 2 digits precision.
:param int precision_digits: number of fractional digits to round to.
:param float precision_rounding: decimal number representing the minimum
:param precision_digits: number of fractional digits to round to.
:param 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 value: value to compare with the precision's zero
:param value: value to compare with the precision's zero
:return: True if ``value`` is considered zero
"""
epsilon = _float_check_precision(precision_digits=precision_digits,
@ -150,7 +196,28 @@ def float_is_zero(value, precision_digits=None, precision_rounding=None):
return value == 0.0 or abs(float_round(value, precision_rounding=epsilon)) < epsilon
def float_compare(value1, value2, precision_digits=None, precision_rounding=None):
@overload
def float_compare(
value1: float,
value2: float,
precision_digits: int,
) -> Literal[-1, 0, 1]: ...
@overload
def float_compare(
value1: float,
value2: float,
precision_rounding: float,
) -> Literal[-1, 0, 1]: ...
def float_compare(
value1: float,
value2: float,
precision_digits: int | None = None,
precision_rounding: float | None = None,
) -> Literal[-1, 0, 1]:
"""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
@ -169,10 +236,10 @@ def float_compare(value1, value2, precision_digits=None, precision_rounding=None
computing the difference, while the latter will round before, giving
different results for e.g. 0.006 and 0.002 at 2 digits precision.
:param float value1: first value to compare
:param float value2: second value to compare
:param int precision_digits: number of fractional digits to round to.
:param float precision_rounding: decimal number representing the minimum
:param value1: first value to compare
:param value2: second value to compare
:param precision_digits: number of fractional digits to round to.
:param precision_rounding: decimal number representing the minimum
non-zero value at the desired precision (for example, 0.01 for a
2-digit precision).
:return: (resp.) -1, 0 or 1, if ``value1`` is (resp.) lower than,
@ -192,15 +259,16 @@ def float_compare(value1, value2, precision_digits=None, precision_rounding=None
return -1 if delta < 0.0 else 1
def float_repr(value, precision_digits):
def float_repr(value: float, precision_digits: int) -> str:
"""Returns a string representation of a float with the
given number of fractional digits. This should not be
used to perform a rounding operation (this is done via
:func:`~.float_round`), but only to produce a suitable
string representation for a float.
:param float value:
:param int precision_digits: number of fractional digits to include in the output
:param value: the value to represent
:param precision_digits: number of fractional digits to include in the output
:return: the string representation of the value
"""
# Can't use str() here because it seems to have an intrinsic
# rounding to 12 significant digits, which causes a loss of
@ -210,7 +278,7 @@ def float_repr(value, precision_digits):
return "%.*f" % (precision_digits, value)
def float_split_str(value, precision_digits):
def float_split_str(value: float, precision_digits: int) -> tuple[str, str]:
"""Splits the given float 'value' in its unitary and decimal parts,
returning each of them as a string, rounding the value using
the provided ``precision_digits`` argument.
@ -227,22 +295,19 @@ def float_split_str(value, precision_digits):
1.1 with precision 3 => ('1', '100')
1.12 with precision 0 => ('1', '')
:param float value: value to split.
:param int precision_digits: number of fractional digits to round to.
:param value: value to split.
:param precision_digits: number of fractional digits to round to.
:return: returns the tuple(<unitary part>, <decimal part>) of the given value
:rtype: tuple(str, str)
"""
value = float_round(value, precision_digits=precision_digits)
value_repr = float_repr(value, precision_digits)
return tuple(value_repr.split('.')) if precision_digits else (value_repr, '')
def float_split(value, precision_digits):
def float_split(value: float, precision_digits: int) -> tuple[int, int]:
""" same as float_split_str() except that it returns the unitary and decimal
parts as integers instead of strings. In case ``precision_digits`` is zero,
0 is always returned as decimal part.
:rtype: tuple(int, int)
"""
units, cents = float_split_str(value, precision_digits)
if not cents:
@ -250,7 +315,11 @@ def float_split(value, precision_digits):
return int(units), int(cents)
def json_float_round(value, precision_digits, rounding_method='HALF-UP'):
def json_float_round(
value: float,
precision_digits: int,
rounding_method: RoundingMethod = 'HALF-UP',
) -> float:
"""Not suitable for float calculations! Similar to float_repr except that it
returns a float suitable for json dump
@ -259,7 +328,7 @@ def json_float_round(value, precision_digits, rounding_method='HALF-UP'):
Unfortunately `json.dumps` does not allow any form of custom float representation,
nor any custom types, everything is serialized from the basic JSON types.
:param int precision_digits: number of fractional digits to round to.
:param precision_digits: number of fractional digits to round to.
: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
@ -290,12 +359,11 @@ _INVERTDICT = {
}
def float_invert(value):
def float_invert(value: float) -> float:
"""Inverts a floating point number with increased accuracy.
:param float value: value to invert.
:param bool store: whether store the result in memory for future calls.
:return: rounded float.
:param value: value to invert.
:return: inverted float.
"""
result = _INVERTDICT.get(value)
if result is None: