[pypy-svn] pypy cmath: (lac, arigo)

arigo commits-noreply at bitbucket.org
Mon Jan 17 14:57:45 CET 2011


Author: Armin Rigo <arigo at tunes.org>
Branch: cmath
Changeset: r40765:a9da0fa7e4c8
Date: 2011-01-17 14:53 +0100
http://bitbucket.org/pypy/pypy/changeset/a9da0fa7e4c8/

Log:	(lac, arigo)

	Starting rewriting the cmath module in RPython.

diff --git a/pypy/module/cmath/__init__.py b/pypy/module/cmath/__init__.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/cmath/__init__.py
@@ -0,0 +1,11 @@
+
+# Package initialisation
+from pypy.interpreter.mixedmodule import MixedModule
+
+class Module(MixedModule):
+    appleveldefs = {
+    }
+
+    interpleveldefs = {
+        'sqrt': 'interp_cmath.wrapped_sqrt',
+    }

diff --git a/pypy/module/cmath/test/test_cmath.py b/pypy/module/cmath/test/test_cmath.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/cmath/test/test_cmath.py
@@ -0,0 +1,114 @@
+from __future__ import with_statement
+from pypy.conftest import gettestobjspace
+import os
+
+
+class AppTestCMath:
+    def setup_class(cls):
+        cls.space = gettestobjspace(usemodules=['cmath'])
+
+    def test_sqrt(self):
+        import cmath
+        assert cmath.sqrt(3+4j) == 2+1j
+
+    def test_acos(self):
+        import cmath
+        assert cmath.acos(0.5+0j) == 1.0471975511965979+0j
+
+
+def parse_testfile(fname):
+    """Parse a file with test values
+
+    Empty lines or lines starting with -- are ignored
+    yields id, fn, arg_real, arg_imag, exp_real, exp_imag
+    """
+    fname = os.path.join(os.path.dirname(__file__), fname)
+    with open(fname) as fp:
+        for line in fp:
+            # skip comment lines and blank lines
+            if line.startswith('--') or not line.strip():
+                continue
+
+            lhs, rhs = line.split('->')
+            id, fn, arg_real, arg_imag = lhs.split()
+            rhs_pieces = rhs.split()
+            exp_real, exp_imag = rhs_pieces[0], rhs_pieces[1]
+            flags = rhs_pieces[2:]
+
+            yield (id, fn,
+                   float(arg_real), float(arg_imag),
+                   float(exp_real), float(exp_imag),
+                   flags
+                  )
+
+
+def test_specific_values(space):
+    #if not float.__getformat__("double").startswith("IEEE"):
+    #    return
+
+    def rect_complex(z):
+        """Wrapped version of rect that accepts a complex number instead of
+        two float arguments."""
+        return cmath.rect(z.real, z.imag)
+
+    def polar_complex(z):
+        """Wrapped version of polar that returns a complex number instead of
+        two floats."""
+        return complex(*polar(z))
+
+    for id, fn, ar, ai, er, ei, flags in parse_testfile(test_file):
+        w_arg = space.newcomplex(ar, ai)
+        w_expected = space.newcomplex(er, ei)
+        if fn == 'rect':
+            function = rect_complex
+        elif fn == 'polar':
+            function = polar_complex
+        else:
+            function = getattr(cmath, fn)
+        if 'divide-by-zero' in flags or 'invalid' in flags:
+            try:
+                actual = function(arg)
+            except ValueError:
+                continue
+            else:
+                self.fail('ValueError not raised in test '
+                      '{}: {}(complex({!r}, {!r}))'.format(id, fn, ar, ai))
+
+        if 'overflow' in flags:
+            try:
+                actual = function(arg)
+            except OverflowError:
+                continue
+            else:
+                self.fail('OverflowError not raised in test '
+                      '{}: {}(complex({!r}, {!r}))'.format(id, fn, ar, ai))
+
+        actual = function(arg)
+
+        if 'ignore-real-sign' in flags:
+            actual = complex(abs(actual.real), actual.imag)
+            expected = complex(abs(expected.real), expected.imag)
+        if 'ignore-imag-sign' in flags:
+            actual = complex(actual.real, abs(actual.imag))
+            expected = complex(expected.real, abs(expected.imag))
+
+        # for the real part of the log function, we allow an
+        # absolute error of up to 2e-15.
+        if fn in ('log', 'log10'):
+            real_abs_err = 2e-15
+        else:
+            real_abs_err = 5e-323
+
+        error_message = (
+            '{}: {}(complex({!r}, {!r}))\n'
+            'Expected: complex({!r}, {!r})\n'
+            'Received: complex({!r}, {!r})\n'
+            'Received value insufficiently close to expected value.'
+            ).format(id, fn, ar, ai,
+                 expected.real, expected.imag,
+                 actual.real, actual.imag)
+        self.rAssertAlmostEqual(expected.real, actual.real,
+                                    abs_err=real_abs_err,
+                                    msg=error_message)
+        self.rAssertAlmostEqual(expected.imag, actual.imag,
+                                    msg=error_message)

diff --git a/lib-python/2.7.0/test/cmath_testcases.txt b/pypy/module/cmath/cmath_testcases.txt
copy from lib-python/2.7.0/test/cmath_testcases.txt
copy to pypy/module/cmath/cmath_testcases.txt

diff --git a/pypy/module/cmath/interp_cmath.py b/pypy/module/cmath/interp_cmath.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/cmath/interp_cmath.py
@@ -0,0 +1,76 @@
+import math
+from pypy.rlib.rarithmetic import copysign
+from pypy.interpreter.gateway import ObjSpace, W_Root
+from pypy.module.cmath import Module
+
+def unaryfn(name):
+    def decorator(c_func):
+        def wrapper(space, w_z):
+            x = space.float_w(space.getattr(w_z, space.wrap('real')))
+            y = space.float_w(space.getattr(w_z, space.wrap('imag')))
+            resx, resy = c_func(x, y)
+            return space.newcomplex(resx, resy)
+        wrapper.unwrap_spec = [ObjSpace, W_Root]
+        globals()['wrapped_' + name] = wrapper
+        return c_func
+    return decorator
+
+
+ at unaryfn('sqrt')
+def c_sqrt(x, y):
+    # Method: use symmetries to reduce to the case when x = z.real and y
+    # = z.imag are nonnegative.  Then the real part of the result is
+    # given by
+    #
+    #   s = sqrt((x + hypot(x, y))/2)
+    #
+    # and the imaginary part is
+    #
+    #   d = (y/2)/s
+    #
+    # If either x or y is very large then there's a risk of overflow in
+    # computation of the expression x + hypot(x, y).  We can avoid this
+    # by rewriting the formula for s as:
+    #
+    #   s = 2*sqrt(x/8 + hypot(x/8, y/8))
+    #
+    # This costs us two extra multiplications/divisions, but avoids the
+    # overhead of checking for x and y large.
+    #
+    # If both x and y are subnormal then hypot(x, y) may also be
+    # subnormal, so will lack full precision.  We solve this by rescaling
+    # x and y by a sufficiently large power of 2 to ensure that x and y
+    # are normal.
+
+    #XXX SPECIAL_VALUE
+
+    if x == 0. and y == 0.:
+        return (0., y)
+
+    ax = math.fabs(x)
+    ay = math.fabs(y)
+
+##    if (ax < DBL_MIN && ay < DBL_MIN && (ax > 0. || ay > 0.)) {
+##        /* here we catch cases where hypot(ax, ay) is subnormal */
+##        ax = ldexp(ax, CM_SCALE_UP);
+##        s = ldexp(sqrt(ax + hypot(ax, ldexp(ay, CM_SCALE_UP))),
+##                  CM_SCALE_DOWN);
+##    } else {
+    ax /= 8.
+    s = 2.*math.sqrt(ax + math.hypot(ax, ay/8.))
+##    }
+
+    d = ay/(2.*s)
+
+    if x >= 0.:
+        return (s, copysign(d, y))
+    else:
+        return (d, copysign(s, y))
+
+
+##@unaryfn
+##def c_acos(x, y):
+##    s1x, s1y = c_sqrt(1.-x, -y)
+##    s2x, s2y = c_sqrt(1.+x, y)
+##        r.real = 2.*atan2(s1.real, s2.real);
+##        r.imag = m_asinh(s2.real*s1.imag - s2.imag*s1.real);


More information about the Pypy-commit mailing list