from __future__ import absolute_import from __future__ import division from __future__ import print_function import decimal import fractions import math import six def _new_round(number, ndigits=None): """ Python implementation of Python 3 round(). Round 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. Delegates to the __round__ method if for some reason this exists. Docs: https://docs.python.org/3/library/functions.html#round Basically all borrowed from https://github.com/python/cpython and https://bitbucket.org/pypy/pypy Note: different interpreters handle Inf, Nan, and overflow issues differently so it's hard to say this fully compatible with the future. """ try: return number.__round__(ndigits) except (AttributeError, NotImplementedError): pass # Decimals and Fractions implement `__round__` on their class in py3. if isinstance(number, decimal.Decimal): return _decimal_round(number, ndigits) if isinstance(number, fractions.Fraction): return _fractions_round(number, ndigits) return_int = False if ndigits is None: return_int = True ndigits = 0 elif not isinstance(ndigits, six.integer_types): raise TypeError("Second argument to round should be integral") # handle nans and infinities as cPython does if math.isnan(number): if return_int: return int(number) raise ValueError("cannot round a NaN") if math.isinf(number): if return_int: return int(number) raise OverflowError("cannot round an infinity") # At this point we assume that `number` is a basic scalar like an `int` or a `float`. # Unfortunately, the interpreters I looked into all use their own version of dtoa # logic that isn't exposed. Instead we look to piggyback the `Decimal` logic because # it seems to have enough of what we want except some edge case error handling. # We'll just have to make due. d = decimal.Decimal.from_float(number) context = decimal.getcontext() # Deal with extreme values for ndigits. # For too many ndigits, x always rounds to itself. # For too few, x always rounds to +-0.0. if d.adjusted() + ndigits + 1 > context.prec: result = number elif ndigits <= context.Etiny(): # return 0.0, but with sign of x result = 0.0 * number else: exp = object.__new__(decimal.Decimal) exp._sign = 0 exp._int = '1' exp._exp = -ndigits exp._is_special = False result = d.quantize(exp, rounding=decimal.ROUND_HALF_EVEN) if return_int: return int(result) return type(number)(result) def _decimal_round(dec_obj, n=None): """Round self to the nearest integer, or to a given precision. If only one argument is supplied, round a finite Decimal instance self to the nearest integer. If self is infinite or a NaN then a Python exception is raised. If self is finite and lies exactly halfway between two integers then it is rounded to the integer with even last digit. >>> round(decimal.Decimal('123.456')) 123 >>> round(decimal.Decimal('-456.789')) -457 >>> round(decimal.Decimal('-3.0')) -3 >>> round(decimal.Decimal('2.5')) 2 >>> round(decimal.Decimal('3.5')) 4 >>> round(decimal.Decimal('Inf')) Traceback (most recent call last): ... OverflowError: cannot round an infinity >>> round(decimal.Decimal('NaN')) Traceback (most recent call last): ... ValueError: cannot round a NaN If a second argument n is supplied, self is rounded to n decimal places using the rounding mode for the current context. For an integer n, round(self, -n) is exactly equivalent to self.quantize(Decimal('1En')). >>> round(decimal.Decimal('123.456'), 0) Decimal('123') >>> round(decimal.Decimal('123.456'), 2) Decimal('123.46') >>> round(decimal.Decimal('123.456'), -2) Decimal('1E+2') >>> round(decimal.Decimal('-Infinity'), 37) Decimal('NaN') >>> round(decimal.Decimal('sNaN123'), 0) Decimal('NaN123') """ if n is not None: # two-argument form: use the equivalent quantize call if not isinstance(n, six.integer_types): raise TypeError("Second argument to round should be integral") exp = object.__new__(decimal.Decimal) exp._sign = 0 exp._int = '1' exp._exp = -n exp._is_special = False return dec_obj.quantize(exp) # one-argument form if dec_obj._is_special: if dec_obj.is_nan(): raise ValueError("cannot round a NaN") else: raise OverflowError("cannot round an infinity") return int(dec_obj._rescale(0, decimal.ROUND_HALF_EVEN)) def _fractions_round(frac_obj, ndigits=None): """Will be round(self, ndigits) in 3.0. Rounds half toward even. """ if ndigits is None: floor, remainder = divmod(frac_obj.numerator, frac_obj.denominator) if remainder * 2 < frac_obj.denominator: return floor elif remainder * 2 > frac_obj.denominator: return floor + 1 # Deal with the half case: elif floor % 2 == 0: return floor else: return floor + 1 shift = 10 ** abs(ndigits) # See _operator_fallbacks.forward to check that the results of # these operations will always be Fraction and therefore have # round(). if ndigits > 0: return fractions.Fraction(round(frac_obj * shift), shift) else: return fractions.Fraction(round(frac_obj / shift) * shift) if round(2.5) != 2: round = _new_round else: round = round