bazarr/libs/future/builtins/newround.py

106 lines
3.1 KiB
Python

"""
``python-future``: pure Python implementation of Python 3 round().
"""
from __future__ import division
from future.utils import PYPY, PY26, bind_method
# Use the decimal module for simplicity of implementation (and
# hopefully correctness).
from decimal import Decimal, ROUND_HALF_EVEN
def newround(number, ndigits=None):
"""
See Python 3 documentation: uses Banker's Rounding.
Delegates to the __round__ method if for some reason this exists.
If not, rounds a number to a given precision in decimal digits (default
0 digits). This returns an int when called with one argument,
otherwise the same type as the number. ndigits may be negative.
See the test_round method in future/tests/test_builtins.py for
examples.
"""
return_int = False
if ndigits is None:
return_int = True
ndigits = 0
if hasattr(number, '__round__'):
return number.__round__(ndigits)
exponent = Decimal('10') ** (-ndigits)
# Work around issue #24: round() breaks on PyPy with NumPy's types
# Also breaks on CPython with NumPy's specialized int types like uint64
if 'numpy' in repr(type(number)):
number = float(number)
if isinstance(number, Decimal):
d = number
else:
if not PY26:
d = Decimal.from_float(number)
else:
d = from_float_26(number)
if ndigits < 0:
result = newround(d / exponent) * exponent
else:
result = d.quantize(exponent, rounding=ROUND_HALF_EVEN)
if return_int:
return int(result)
else:
return float(result)
### From Python 2.7's decimal.py. Only needed to support Py2.6:
def from_float_26(f):
"""Converts a float to a decimal number, exactly.
Note that Decimal.from_float(0.1) is not the same as Decimal('0.1').
Since 0.1 is not exactly representable in binary floating point, the
value is stored as the nearest representable value which is
0x1.999999999999ap-4. The exact equivalent of the value in decimal
is 0.1000000000000000055511151231257827021181583404541015625.
>>> Decimal.from_float(0.1)
Decimal('0.1000000000000000055511151231257827021181583404541015625')
>>> Decimal.from_float(float('nan'))
Decimal('NaN')
>>> Decimal.from_float(float('inf'))
Decimal('Infinity')
>>> Decimal.from_float(-float('inf'))
Decimal('-Infinity')
>>> Decimal.from_float(-0.0)
Decimal('-0')
"""
import math as _math
from decimal import _dec_from_triple # only available on Py2.6 and Py2.7 (not 3.3)
if isinstance(f, (int, long)): # handle integer inputs
return Decimal(f)
if _math.isinf(f) or _math.isnan(f): # raises TypeError if not a float
return Decimal(repr(f))
if _math.copysign(1.0, f) == 1.0:
sign = 0
else:
sign = 1
n, d = abs(f).as_integer_ratio()
# int.bit_length() method doesn't exist on Py2.6:
def bit_length(d):
if d != 0:
return len(bin(abs(d))) - 2
else:
return 0
k = bit_length(d) - 1
result = _dec_from_triple(sign, str(n*5**k), -k)
return result
__all__ = ['newround']