Author: Philip Jenvey <[email protected]>
Branch: py3k
Changeset: r58260:5460b861cac5
Date: 2012-10-19 11:52 -0700
http://bitbucket.org/pypy/pypy/changeset/5460b861cac5/

Log:    py3 round

diff --git a/pypy/module/__builtin__/operation.py 
b/pypy/module/__builtin__/operation.py
--- a/pypy/module/__builtin__/operation.py
+++ b/pypy/module/__builtin__/operation.py
@@ -3,11 +3,9 @@
 """
 
 from pypy.interpreter import gateway
-from pypy.interpreter.error import OperationError
+from pypy.interpreter.error import OperationError, operationerrfmt
 from pypy.interpreter.gateway import unwrap_spec, WrappedDefault
 from pypy.rlib.runicode import UNICHR
-from pypy.rlib.rfloat import isnan, isinf, round_double
-from pypy.rlib import rfloat
 import __builtin__
 
 def abs(space, w_val):
@@ -102,40 +100,22 @@
 
 # ____________________________________________________________
 
-# Here 0.30103 is an upper bound for log10(2)
-NDIGITS_MAX = int((rfloat.DBL_MANT_DIG - rfloat.DBL_MIN_EXP) * 0.30103)
-NDIGITS_MIN = -int((rfloat.DBL_MAX_EXP + 1) * 0.30103)
-
-@unwrap_spec(number=float, w_ndigits = WrappedDefault(0))
-def round(space, number, w_ndigits):
-    """round(number[, ndigits]) -> floating point number
+@unwrap_spec(w_ndigits=WrappedDefault(None))
+def round(space, w_number, w_ndigits=None):
+    """round(number[, ndigits]) -> number
 
 Round a number to a given precision in decimal digits (default 0 digits).
-This always returns a floating point number.  Precision may be negative."""
-    # Algorithm copied directly from CPython
-
-    # interpret 2nd argument as a Py_ssize_t; clip on overflow
-    ndigits = space.getindex_w(w_ndigits, None)
-
-    # nans, infinities and zeros round to themselves
-    if number == 0 or isinf(number) or isnan(number):
-        return space.wrap(number)
-
-    # Deal with extreme values for ndigits. For ndigits > NDIGITS_MAX, x
-    # always rounds to itself.  For ndigits < NDIGITS_MIN, x always
-    # rounds to +-0.0.
-    if ndigits > NDIGITS_MAX:
-        return space.wrap(number)
-    elif ndigits < NDIGITS_MIN:
-        # return 0.0, but with sign of x
-        return space.wrap(0.0 * number)
-
-    # finite x, and ndigits is not unreasonably large
-    z = round_double(number, ndigits)
-    if isinf(z):
-        raise OperationError(space.w_OverflowError,
-                             space.wrap("rounded value too large to 
represent"))
-    return space.wrap(z)
+This returns an int when called with one argument, otherwise the
+same type as the number. ndigits may be negative."""
+    round = space.lookup(w_number, '__round__')
+    if round is None:
+        raise operationerrfmt(space.w_TypeError,
+                              "type %s doesn't define __round__ method",
+                              space.type(w_number).getname(space))
+    if space.is_none(w_ndigits):
+        return space.get_and_call_function(round, w_number)
+    else:
+        return space.get_and_call_function(round, w_number, w_ndigits)
 
 # ____________________________________________________________
 
diff --git a/pypy/module/__builtin__/test/test_builtin.py 
b/pypy/module/__builtin__/test/test_builtin.py
--- a/pypy/module/__builtin__/test/test_builtin.py
+++ b/pypy/module/__builtin__/test/test_builtin.py
@@ -642,6 +642,77 @@
         assert round(562949953421312.5, 1) == 562949953421312.5
         assert round(56294995342131.5, 3) == 56294995342131.5
 
+        assert round(0.0) == 0.0
+        assert type(round(0.0)) == int
+        assert round(1.0) == 1.0
+        assert round(10.0) == 10.0
+        assert round(1000000000.0) == 1000000000.0
+        assert round(1e20) == 1e20
+
+        assert round(-1.0) == -1.0
+        assert round(-10.0) == -10.0
+        assert round(-1000000000.0) == -1000000000.0
+        assert round(-1e20) == -1e20
+
+        assert round(0.1) == 0.0
+        assert round(1.1) == 1.0
+        assert round(10.1) == 10.0
+        assert round(1000000000.1) == 1000000000.0
+
+        assert round(-1.1) == -1.0
+        assert round(-10.1) == -10.0
+        assert round(-1000000000.1) == -1000000000.0
+
+        assert round(0.9) == 1.0
+        assert round(9.9) == 10.0
+        assert round(999999999.9) == 1000000000.0
+
+        assert round(-0.9) == -1.0
+        assert round(-9.9) == -10.0
+        assert round(-999999999.9) == -1000000000.0
+
+        assert round(-8.0, -1) == -10.0
+        assert type(round(-8.0, -1)) == float
+
+        assert type(round(-8.0, 0)) == float
+        assert type(round(-8.0, 1)) == float
+
+        # Check even / odd rounding behaviour
+        assert round(5.5) == 6
+        assert round(6.5) == 6
+        assert round(-5.5) == -6
+        assert round(-6.5) == -6
+
+        # Check behavior on ints
+        assert round(0) == 0
+        assert round(8) == 8
+        assert round(-8) == -8
+        assert type(round(0)) == int
+        assert type(round(-8, -1)) == int
+        assert type(round(-8, 0)) == int
+        assert type(round(-8, 1)) == int
+
+        assert round(number=-8.0, ndigits=-1) == -10.0
+        raises(TypeError, round)
+
+        # test generic rounding delegation for reals
+        class TestRound:
+            def __round__(self):
+                return 23
+
+        class TestNoRound:
+            pass
+
+        assert round(TestRound()) == 23
+
+        raises(TypeError, round, 1, 2, 3)
+        raises(TypeError, round, TestNoRound())
+
+        t = TestNoRound()
+        t.__round__ = lambda *args: args
+        raises(TypeError, round, t)
+        raises(TypeError, round, t, 0)
+
     def test_vars_obscure_case(self):
         class C_get_vars(object):
             def getDict(self):
diff --git a/pypy/objspace/std/floattype.py b/pypy/objspace/std/floattype.py
--- a/pypy/objspace/std/floattype.py
+++ b/pypy/objspace/std/floattype.py
@@ -263,6 +263,58 @@
 def descr_get_imag(space, w_obj):
     return space.wrap(0.0)
 
+# Here 0.30103 is an upper bound for log10(2)
+NDIGITS_MAX = int((rfloat.DBL_MANT_DIG - rfloat.DBL_MIN_EXP) * 0.30103)
+NDIGITS_MIN = -int((rfloat.DBL_MAX_EXP + 1) * 0.30103)
+
+@unwrap_spec(w_ndigits=WrappedDefault(None))
+def descr___round__(space, w_float, w_ndigits=None):
+    # Algorithm copied directly from CPython
+    from pypy.objspace.std.floatobject import W_FloatObject
+    from pypy.objspace.std.longobject import W_LongObject
+    assert isinstance(w_float, W_FloatObject)
+    x = w_float.floatval
+
+    if space.is_none(w_ndigits):
+        # single-argument round: round to nearest integer
+        rounded = rfloat.round_away(x)
+        if math.fabs(x - rounded) == 0.5:
+            # halfway case: round to even
+            rounded = 2.0 * rfloat.round_away(x / 2.0)
+        try:
+            return W_LongObject.fromfloat(space, rounded)
+        except OverflowError:
+            raise OperationError(
+                space.w_OverflowError,
+                space.wrap("cannot convert float infinity to integer"))
+        except ValueError:
+            raise OperationError(
+                space.w_ValueError,
+                space.wrap("cannot convert float NaN to integer"))
+
+    # interpret 2nd argument as a Py_ssize_t; clip on overflow
+    ndigits = space.getindex_w(w_ndigits, None)
+
+    # nans and infinities round to themselves
+    if rfloat.isinf(x) or rfloat.isnan(x):
+        return space.wrap(x)
+
+    # Deal with extreme values for ndigits. For ndigits > NDIGITS_MAX, x
+    # always rounds to itself.  For ndigits < NDIGITS_MIN, x always
+    # rounds to +-0.0
+    if ndigits > NDIGITS_MAX:
+        return space.wrap(x)
+    elif ndigits < NDIGITS_MIN:
+        # return 0.0, but with sign of x
+        return space.wrap(0.0 * x)
+
+    # finite x, and ndigits is not unreasonably large
+    z = rfloat.round_double(x, ndigits)
+    if rfloat.isinf(z):
+        raise OperationError(space.w_OverflowError,
+                             space.wrap("overflow occurred during round"))
+    return space.wrap(z)
+
 # ____________________________________________________________
 
 float_typedef = StdTypeDef("float",
@@ -271,6 +323,7 @@
 Convert a string or number to a floating point number, if possible.''',
     __new__ = interp2app(descr__new__),
     __getformat__ = interp2app(descr___getformat__, as_classmethod=True),
+    __round__ = interp2app(descr___round__),
     fromhex = interp2app(descr_fromhex, as_classmethod=True),
     conjugate = interp2app(descr_conjugate),
     real = typedef.GetSetProperty(descr_get_real),
diff --git a/pypy/objspace/std/longtype.py b/pypy/objspace/std/longtype.py
--- a/pypy/objspace/std/longtype.py
+++ b/pypy/objspace/std/longtype.py
@@ -1,9 +1,11 @@
 from pypy.interpreter.error import OperationError
 from pypy.interpreter import typedef
-from pypy.interpreter.gateway import interp2app, unwrap_spec, WrappedDefault
+from pypy.interpreter.gateway import (applevel, interp2app, unwrap_spec,
+                                      WrappedDefault)
 from pypy.objspace.std.register_all import register_all
 from pypy.objspace.std.stdtypedef import StdTypeDef, SMM
 from pypy.objspace.std.strutil import string_to_bigint, ParseStringError
+from pypy.rlib.rbigint import rbigint
 
 def descr_conjugate(space, w_int):
     return space.int(w_int)
@@ -12,7 +14,6 @@
 @unwrap_spec(w_x = WrappedDefault(0))
 def descr__new__(space, w_longtype, w_x, w_base=None):
     from pypy.objspace.std.longobject import W_LongObject
-    from pypy.rlib.rbigint import rbigint
     if space.config.objspace.std.withsmalllong:
         from pypy.objspace.std.smalllongobject import W_SmallLongObject
     else:
@@ -120,13 +121,64 @@
 
 @unwrap_spec(s='bufferstr', byteorder=str)
 def descr_from_bytes(space, w_cls, s, byteorder):
-    from pypy.rlib.rbigint import rbigint
     bigint = rbigint.frombytes(s)
     from pypy.objspace.std.longobject import W_LongObject
     w_obj = space.allocate_instance(W_LongObject, w_cls)
     W_LongObject.__init__(w_obj, bigint)
     return w_obj
 
+divmod_near = applevel('''
+       def divmod_near(a, b):
+           """Return a pair (q, r) such that a = b * q + r, and abs(r)
+           <= abs(b)/2, with equality possible only if q is even.  In
+           other words, q == a / b, rounded to the nearest integer using
+           round-half-to-even."""
+           q, r = divmod(a, b)
+           # round up if either r / b > 0.5, or r / b == 0.5 and q is
+           # odd.  The expression r / b > 0.5 is equivalent to 2 * r > b
+           # if b is positive, 2 * r < b if b negative.
+           greater_than_half = 2*r > b if b > 0 else 2*r < b
+           exactly_half = 2*r == b
+           if greater_than_half or exactly_half and q % 2 == 1:
+               q += 1
+               r -= b
+           return q, r
+''', filename=__file__).interphook('divmod_near')
+
+@unwrap_spec(w_ndigits=WrappedDefault(None))
+def descr___round__(space, w_long, w_ndigits=None):
+    """To round an integer m to the nearest 10**n (n positive), we make
+    use of the divmod_near operation, defined by:
+
+    divmod_near(a, b) = (q, r)
+
+    where q is the nearest integer to the quotient a / b (the
+    nearest even integer in the case of a tie) and r == a - q * b.
+    Hence q * b = a - r is the nearest multiple of b to a,
+    preferring even multiples in the case of a tie.
+
+    So the nearest multiple of 10**n to m is:
+
+    m - divmod_near(m, 10**n)[1]
+
+    """
+    from pypy.objspace.std.longobject import W_AbstractIntObject, newlong
+    assert isinstance(w_long, W_AbstractIntObject)
+
+    if space.is_none(w_ndigits):
+        return space.int(w_long)
+
+    ndigits = space.bigint_w(space.index(w_ndigits))
+    # if ndigits >= 0 then no rounding is necessary; return self unchanged
+    if ndigits.ge(rbigint.fromint(0)):
+        return space.int(w_long)
+
+    # result = self - divmod_near(self, 10 ** -ndigits)[1]
+    right = rbigint.fromint(10).pow(ndigits.neg())
+    w_temp = divmod_near(space, w_long, newlong(space, right))
+    w_temp2 = space.getitem(w_temp, space.wrap(1))
+    return space.sub(w_long, w_temp2)
+
 # ____________________________________________________________
 
 long_typedef = StdTypeDef("int",
@@ -138,6 +190,7 @@
 string, use the optional base.  It is an error to supply a base when
 converting a non-string.''',
     __new__ = interp2app(descr__new__),
+    __round__ = interp2app(descr___round__),
     conjugate = interp2app(descr_conjugate),
     numerator = typedef.GetSetProperty(descr_get_numerator),
     denominator = typedef.GetSetProperty(descr_get_denominator),
_______________________________________________
pypy-commit mailing list
[email protected]
http://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to