106 lines
3.1 KiB
Python
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']
|