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