Skip to content

Instantly share code, notes, and snippets.

@mastermatt
Last active December 5, 2019 09:09
Show Gist options
  • Select an option

  • Save mastermatt/887d41bf7d30a72baee27a30661fd77a to your computer and use it in GitHub Desktop.

Select an option

Save mastermatt/887d41bf7d30a72baee27a30661fd77a to your computer and use it in GitHub Desktop.

Revisions

  1. mastermatt revised this gist Jul 31, 2016. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions round_compat.py
    Original 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)

  2. mastermatt created this gist Jul 31, 2016.
    185 changes: 185 additions & 0 deletions round_compat.py
    Original 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