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

arigo commits-noreply at bitbucket.org
Mon Jan 17 16:09:08 CET 2011


Author: Armin Rigo <arigo at tunes.org>
Branch: cmath
Changeset: r40770:583d67bc6e4a
Date: 2011-01-17 16:05 +0100
http://bitbucket.org/pypy/pypy/changeset/583d67bc6e4a/

Log:	(lac, arigo)

	Fix the test runner to load cmath_testcases.txt from cpython. Start
	implementing acos.

diff --git a/pypy/module/cmath/constant.py b/pypy/module/cmath/constant.py
--- a/pypy/module/cmath/constant.py
+++ b/pypy/module/cmath/constant.py
@@ -1,3 +1,5 @@
+import math
+from pypy.rlib.rarithmetic import isinf
 from pypy.rpython.tool import rffi_platform
 from pypy.translator.tool.cbuild import ExternalCompilationInfo
 
@@ -5,6 +7,7 @@
 class CConfig:
     _compilation_info_ = ExternalCompilationInfo(includes=['float.h'])
 
+    DBL_MAX = rffi_platform.DefinedConstantDouble('DBL_MAX')
     DBL_MIN = rffi_platform.DefinedConstantDouble('DBL_MIN')
     DBL_MANT_DIG = rffi_platform.ConstantInteger('DBL_MANT_DIG')
 
@@ -14,10 +17,26 @@
     globals()[k] = v
 
 
+assert 0.0 < DBL_MAX < (1e200*1e200)
+assert isinf(DBL_MAX * 1.0001)
 assert DBL_MIN > 0.0
 assert DBL_MIN * (2**-53) == 0.0
 
 
+# Constants.
+M_LN2 = 0.6931471805599453094   # natural log of 2
+M_LN10 = 2.302585092994045684   # natural log of 10
+
+
+# CM_LARGE_DOUBLE is used to avoid spurious overflow in the sqrt, log,
+# inverse trig and inverse hyperbolic trig functions.  Its log is used in the
+# evaluation of exp, cos, cosh, sin, sinh, tan, and tanh to avoid unecessary
+# overflow.
+CM_LARGE_DOUBLE = DBL_MAX/4.
+CM_SQRT_LARGE_DOUBLE = math.sqrt(CM_LARGE_DOUBLE)
+CM_LOG_LARGE_DOUBLE = math.log(CM_LARGE_DOUBLE)
+CM_SQRT_DBL_MIN = math.sqrt(DBL_MIN)
+
 # CM_SCALE_UP is an odd integer chosen such that multiplication by
 # 2**CM_SCALE_UP is sufficient to turn a subnormal into a normal.
 # CM_SCALE_DOWN is (-(CM_SCALE_UP+1)/2).  These scalings are used to compute

diff --git a/pypy/module/cmath/test/test_cmath.py b/pypy/module/cmath/test/test_cmath.py
--- a/pypy/module/cmath/test/test_cmath.py
+++ b/pypy/module/cmath/test/test_cmath.py
@@ -1,7 +1,8 @@
 from __future__ import with_statement
 from pypy.conftest import gettestobjspace
-from pypy.rlib.rarithmetic import copysign
-import os
+from pypy.rlib.rarithmetic import copysign, isnan, isinf
+from pypy.module.cmath import interp_cmath
+import os, math
 
 
 def test_special_values():
@@ -38,10 +39,6 @@
         assert math.isinf(z.real) and z.real > 0.0
         assert z.imag == 0.0 and math.copysign(1., z.imag) == -1.
 
-    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
@@ -68,30 +65,79 @@
                    flags
                   )
 
+def rAssertAlmostEqual(a, b, rel_err = 2e-15, abs_err = 5e-323, msg=''):
+    """Fail if the two floating-point numbers are not almost equal.
 
-def test_specific_values(space):
+    Determine whether floating-point values a and b are equal to within
+    a (small) rounding error.  The default values for rel_err and
+    abs_err are chosen to be suitable for platforms where a float is
+    represented by an IEEE 754 double.  They allow an error of between
+    9 and 19 ulps.
+    """
+
+    # special values testing
+    if isnan(a):
+        if isnan(b):
+            return
+        raise AssertionError(msg + '%r should be nan' % (b,))
+
+    if isinf(a):
+        if a == b:
+            return
+        raise AssertionError(msg + 'finite result where infinity expected: '
+                                   'expected %r, got %r' % (a, b))
+
+    # if both a and b are zero, check whether they have the same sign
+    # (in theory there are examples where it would be legitimate for a
+    # and b to have opposite signs; in practice these hardly ever
+    # occur).
+    if not a and not b:
+        if copysign(1., a) != copysign(1., b):
+            raise AssertionError(msg + 'zero has wrong sign: expected %r, '
+                                       'got %r' % (a, b))
+
+    # if a-b overflows, or b is infinite, return False.  Again, in
+    # theory there are examples where a is within a few ulps of the
+    # max representable float, and then b could legitimately be
+    # infinite.  In practice these examples are rare.
+    try:
+        absolute_error = abs(b-a)
+    except OverflowError:
+        pass
+    else:
+        # test passes if either the absolute error or the relative
+        # error is sufficiently small.  The defaults amount to an
+        # error of between 9 ulps and 19 ulps on an IEEE-754 compliant
+        # machine.
+        if absolute_error <= max(abs_err, rel_err * abs(a)):
+            return
+    raise AssertionError(msg + '%r and %r are not sufficiently close' % (a, b))
+
+def test_specific_values():
     #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."""
+        xxx
         return cmath.rect(z.real, z.imag)
 
     def polar_complex(z):
         """Wrapped version of polar that returns a complex number instead of
         two floats."""
+        xxx
         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)
+    for id, fn, ar, ai, er, ei, flags in parse_testfile('cmath_testcases.txt'):
+        arg = (ar, ai)
+        expected = (er, ei)
         if fn == 'rect':
             function = rect_complex
         elif fn == 'polar':
             function = polar_complex
         else:
-            function = getattr(cmath, fn)
+            function = getattr(interp_cmath, 'c_' + fn)
         if 'divide-by-zero' in flags or 'invalid' in flags:
             try:
                 actual = function(arg)
@@ -110,7 +156,7 @@
                 self.fail('OverflowError not raised in test '
                       '{}: {}(complex({!r}, {!r}))'.format(id, fn, ar, ai))
 
-        actual = function(arg)
+        actual = function(*arg)
 
         if 'ignore-real-sign' in flags:
             actual = complex(abs(actual.real), actual.imag)
@@ -127,15 +173,15 @@
             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)
+            '%s: %s(complex(%r, %r))\n'
+            'Expected: complex(%r, %r)\n'
+            'Received: complex(%r, %r)\n'
+            ) % (id, fn, ar, ai,
+                 expected[0], expected[1],
+                 actual[0], actual[1])
+
+        rAssertAlmostEqual(expected[0], actual[0],
+                           abs_err=real_abs_err,
+                           msg=error_message)
+        rAssertAlmostEqual(expected[1], actual[1],
+                           msg=error_message)

diff --git a/pypy/module/cmath/interp_cmath.py b/pypy/module/cmath/interp_cmath.py
--- a/pypy/module/cmath/interp_cmath.py
+++ b/pypy/module/cmath/interp_cmath.py
@@ -1,26 +1,29 @@
 import math
-from pypy.rlib.rarithmetic import copysign
+from math import fabs
+from pypy.rlib.rarithmetic import copysign, asinh
 from pypy.interpreter.gateway import ObjSpace, W_Root
 from pypy.module.cmath import Module
 from pypy.module.cmath.constant import DBL_MIN, CM_SCALE_UP, CM_SCALE_DOWN
+from pypy.module.cmath.constant import CM_LARGE_DOUBLE, M_LN2
 from pypy.module.cmath.special_value import isfinite, special_type
 from pypy.module.cmath.special_value import sqrt_special_values
 
 
-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
+def unaryfn(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)
+    #
+    name = c_func.func_name
+    assert name.startswith('c_')
+    wrapper.unwrap_spec = [ObjSpace, W_Root]
+    globals()['wrapped_' + name[2:]] = wrapper
+    return c_func
 
 
- at unaryfn('sqrt')
+ at unaryfn
 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
@@ -52,8 +55,8 @@
     if x == 0. and y == 0.:
         return (0., y)
 
-    ax = math.fabs(x)
-    ay = math.fabs(y)
+    ax = fabs(x)
+    ay = fabs(y)
 
     if ax < DBL_MIN and ay < DBL_MIN and (ax > 0. or ay > 0.):
         # here we catch cases where hypot(ax, ay) is subnormal
@@ -73,9 +76,22 @@
         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);
+ at unaryfn
+def c_acos(x, y):
+    if fabs(x) > CM_LARGE_DOUBLE or fabs(y) > CM_LARGE_DOUBLE:
+        # avoid unnecessary overflow for large arguments
+        real = math.atan2(fabs(y), x)
+        # split into cases to make sure that the branch cut has the
+        # correct continuity on systems with unsigned zeros
+        if x < 0.:
+            imag = -copysign(math.log(math.hypot(x/2., y/2.)) +
+                             M_LN2*2., y)
+        else:
+            imag = copysign(math.log(math.hypot(x/2., y/2.)) +
+                            M_LN2*2., -y)
+    else:
+        s1x, s1y = c_sqrt(1.-x, -y)
+        s2x, s2y = c_sqrt(1.+x, y)
+        real = 2.*math.atan2(s1x, s2x)
+        imag = asinh(s2x*s1y - s2y*s1x)
+    return (real, imag)


More information about the Pypy-commit mailing list