newround.py 3.1 KB

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