py23: only use the slow round3 implementation for half-way, even cases; add backport of py35 math.isclose()
This commit is contained in:
parent
2df7241f9c
commit
b09501fd8b
@ -259,7 +259,48 @@ def xrange(*args, **kwargs):
|
|||||||
raise Py23Error("'xrange' is not defined. Use 'range' instead.")
|
raise Py23Error("'xrange' is not defined. Use 'range' instead.")
|
||||||
|
|
||||||
|
|
||||||
import decimal as _decimal
|
import math as _math
|
||||||
|
|
||||||
|
try:
|
||||||
|
isclose = _math.isclose
|
||||||
|
except AttributeError:
|
||||||
|
# math.isclose() was only added in Python 3.5
|
||||||
|
|
||||||
|
_isinf = _math.isinf
|
||||||
|
_fabs = _math.fabs
|
||||||
|
|
||||||
|
def isclose(a, b, rel_tol=1e-09, abs_tol=0):
|
||||||
|
"""
|
||||||
|
Python 2 implementation of Python 3.5 math.isclose()
|
||||||
|
https://hg.python.org/cpython/file/v3.5.2/Modules/mathmodule.c#l1993
|
||||||
|
"""
|
||||||
|
# sanity check on the inputs
|
||||||
|
if rel_tol < 0 or abs_tol < 0:
|
||||||
|
raise ValueError("tolerances must be non-negative")
|
||||||
|
# short circuit exact equality -- needed to catch two infinities of
|
||||||
|
# the same sign. And perhaps speeds things up a bit sometimes.
|
||||||
|
if a == b:
|
||||||
|
return True
|
||||||
|
# This catches the case of two infinities of opposite sign, or
|
||||||
|
# one infinity and one finite number. Two infinities of opposite
|
||||||
|
# sign would otherwise have an infinite relative tolerance.
|
||||||
|
# Two infinities of the same sign are caught by the equality check
|
||||||
|
# above.
|
||||||
|
if _isinf(a) or _isinf(b):
|
||||||
|
return False
|
||||||
|
# Cast to float to allow decimal.Decimal arguments
|
||||||
|
if not isinstance(a, float):
|
||||||
|
a = float(a)
|
||||||
|
if not isinstance(b, float):
|
||||||
|
b = float(b)
|
||||||
|
# now do the regular computation
|
||||||
|
# this is essentially the "weak" test from the Boost library
|
||||||
|
diff = _fabs(b - a)
|
||||||
|
result = ((diff <= _fabs(rel_tol * a)) or
|
||||||
|
(diff <= _fabs(rel_tol * b)) or
|
||||||
|
(diff <= abs_tol))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
if PY3:
|
if PY3:
|
||||||
def round2(number, ndigits=None):
|
def round2(number, ndigits=None):
|
||||||
@ -297,6 +338,12 @@ if PY3:
|
|||||||
round = round3 = round
|
round = round3 = round
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
import decimal as _decimal
|
||||||
|
|
||||||
|
# in Python 2, 'round2' is an alias to the built-in 'round' and
|
||||||
|
# 'round' is shadowed by 'round3'
|
||||||
|
round2 = round
|
||||||
|
|
||||||
def round3(number, ndigits=None):
|
def round3(number, ndigits=None):
|
||||||
"""
|
"""
|
||||||
Implementation of Python 3 built-in round() function.
|
Implementation of Python 3 built-in round() function.
|
||||||
@ -330,24 +377,28 @@ else:
|
|||||||
# return the same type as the number, when called with two arguments
|
# return the same type as the number, when called with two arguments
|
||||||
totype = type(number)
|
totype = type(number)
|
||||||
|
|
||||||
if ndigits < 0:
|
m = number * (10 ** ndigits)
|
||||||
exponent = 10 ** (-ndigits)
|
# if number is half-way between two multiples, and the mutliple that is
|
||||||
quotient, remainder = divmod(number, exponent)
|
# closer to zero is even, we use the (slow) pure-Python implementation
|
||||||
half = exponent//2
|
if isclose(m % 1, .5) and int(m) % 2 == 0:
|
||||||
if remainder > half or (remainder == half and quotient % 2 != 0):
|
if ndigits < 0:
|
||||||
quotient += 1
|
exponent = 10 ** (-ndigits)
|
||||||
d = quotient * exponent
|
quotient, remainder = divmod(number, exponent)
|
||||||
else:
|
half = exponent//2
|
||||||
exponent = _decimal.Decimal('10') ** (-ndigits) if ndigits != 0 else 1
|
if remainder > half or (remainder == half and quotient % 2 != 0):
|
||||||
|
quotient += 1
|
||||||
|
d = quotient * exponent
|
||||||
|
else:
|
||||||
|
exponent = _decimal.Decimal('10') ** (-ndigits) if ndigits != 0 else 1
|
||||||
|
|
||||||
d = _decimal.Decimal.from_float(number).quantize(
|
d = _decimal.Decimal.from_float(number).quantize(
|
||||||
exponent, rounding=_decimal.ROUND_HALF_EVEN)
|
exponent, rounding=_decimal.ROUND_HALF_EVEN)
|
||||||
|
else:
|
||||||
|
# else we use the built-in round() as it produces the same results
|
||||||
|
d = round2(number, ndigits)
|
||||||
|
|
||||||
return totype(d)
|
return totype(d)
|
||||||
|
|
||||||
# in Python 2, 'round2' is an alias to the built-in 'round' and
|
|
||||||
# 'round' is shadowed by 'round3'
|
|
||||||
round2 = round
|
|
||||||
round = round3
|
round = round3
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user