[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