newround.py 3.1 KB

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