1"""
2``python-future``: pure Python implementation of Python 3 round().
3"""
4
5from future.utils import PYPY, PY26, bind_method
6
7# Use the decimal module for simplicity of implementation (and
8# hopefully correctness).
9from decimal import Decimal, ROUND_HALF_EVEN
10
11
12def newround(number, ndigits=None):
13    """
14    See Python 3 documentation: uses Banker's Rounding.
15
16    Delegates to the __round__ method if for some reason this exists.
17
18    If not, rounds a number to a given precision in decimal digits (default
19    0 digits). This returns an int when called with one argument,
20    otherwise the same type as the number. ndigits may be negative.
21
22    See the test_round method in future/tests/test_builtins.py for
23    examples.
24    """
25    return_int = False
26    if ndigits is None:
27        return_int = True
28        ndigits = 0
29    if hasattr(number, '__round__'):
30        return number.__round__(ndigits)
31
32    if ndigits < 0:
33        raise NotImplementedError('negative ndigits not supported yet')
34    exponent = Decimal('10') ** (-ndigits)
35
36    if PYPY:
37        # Work around issue #24: round() breaks on PyPy with NumPy's types
38        if 'numpy' in repr(type(number)):
39            number = float(number)
40
41    if isinstance(number, Decimal):
42        d = number
43    else:
44        if not PY26:
45            d = Decimal.from_float(number).quantize(exponent,
46                                                rounding=ROUND_HALF_EVEN)
47        else:
48            d = from_float_26(number).quantize(exponent, rounding=ROUND_HALF_EVEN)
49
50    if return_int:
51        return int(d)
52    else:
53        return float(d)
54
55
56### From Python 2.7's decimal.py. Only needed to support Py2.6:
57
58def from_float_26(f):
59    """Converts a float to a decimal number, exactly.
60
61    Note that Decimal.from_float(0.1) is not the same as Decimal('0.1').
62    Since 0.1 is not exactly representable in binary floating point, the
63    value is stored as the nearest representable value which is
64    0x1.999999999999ap-4.  The exact equivalent of the value in decimal
65    is 0.1000000000000000055511151231257827021181583404541015625.
66
67    >>> Decimal.from_float(0.1)
68    Decimal('0.1000000000000000055511151231257827021181583404541015625')
69    >>> Decimal.from_float(float('nan'))
70    Decimal('NaN')
71    >>> Decimal.from_float(float('inf'))
72    Decimal('Infinity')
73    >>> Decimal.from_float(-float('inf'))
74    Decimal('-Infinity')
75    >>> Decimal.from_float(-0.0)
76    Decimal('-0')
77
78    """
79    import math as _math
80    from decimal import _dec_from_triple    # only available on Py2.6 and Py2.7 (not 3.3)
81
82    if isinstance(f, (int, long)):        # handle integer inputs
83        return Decimal(f)
84    if _math.isinf(f) or _math.isnan(f):  # raises TypeError if not a float
85        return Decimal(repr(f))
86    if _math.copysign(1.0, f) == 1.0:
87        sign = 0
88    else:
89        sign = 1
90    n, d = abs(f).as_integer_ratio()
91    # int.bit_length() method doesn't exist on Py2.6:
92    def bit_length(d):
93        if d != 0:
94            return len(bin(abs(d))) - 2
95        else:
96            return 0
97    k = bit_length(d) - 1
98    result = _dec_from_triple(sign, str(n*5**k), -k)
99    return result
100
101
102__all__ = ['newround']
103