Last active
December 5, 2019 09:09
-
-
Save mastermatt/887d41bf7d30a72baee27a30661fd77a to your computer and use it in GitHub Desktop.
Revisions
-
mastermatt revised this gist
Jul 31, 2016 . 1 changed file with 1 addition and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -33,6 +33,7 @@ def _new_round(number, ndigits=None): except (AttributeError, NotImplementedError): pass # Decimals and Fractions implement `__round__` on their class in py3. if isinstance(number, decimal.Decimal): return _decimal_round(number, ndigits) -
mastermatt created this gist
Jul 31, 2016 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,185 @@ 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 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