[pypy-svn] r72292 - in pypy/trunk/pypy: module/math module/math/test rpython rpython/lltypesystem/module rpython/lltypesystem/module/test

arigo at codespeak.net arigo at codespeak.net
Tue Mar 16 16:50:44 CET 2010


Author: arigo
Date: Tue Mar 16 16:50:42 2010
New Revision: 72292

Added:
   pypy/trunk/pypy/module/math/test/   (props changed)
      - copied from r72291, pypy/branch/ll_math/pypy/module/math/test/
   pypy/trunk/pypy/rpython/lltypesystem/module/test/test_ll_math.py
      - copied unchanged from r72291, pypy/branch/ll_math/pypy/rpython/lltypesystem/module/test/test_ll_math.py
   pypy/trunk/pypy/rpython/lltypesystem/module/test/test_llinterp_math.py
      - copied unchanged from r72291, pypy/branch/ll_math/pypy/rpython/lltypesystem/module/test/test_llinterp_math.py
Removed:
   pypy/trunk/pypy/module/math/readme.test
Modified:
   pypy/trunk/pypy/rpython/extfuncregistry.py
   pypy/trunk/pypy/rpython/lltypesystem/module/ll_math.py
Log:
Improve error testing in the math module, following CPython 2.6.
This should be safe against the underlying C-level functions like
sqrt() setting errno or not.  Also improves testing of this.

(merged from branch/ll_math)


Modified: pypy/trunk/pypy/rpython/extfuncregistry.py
==============================================================================
--- pypy/trunk/pypy/rpython/extfuncregistry.py	(original)
+++ pypy/trunk/pypy/rpython/extfuncregistry.py	Tue Mar 16 16:50:42 2010
@@ -29,8 +29,8 @@
     ('frexp', [float],        (float, int)),
     ('ldexp', [float, int],   float),
     ('modf',  [float],        (float, float)),
-    ] + [(name, [float, float], float) for name in
-         ll_math.binary_math_functions]
+    ] + [(name, [float, float], float)
+         for name in 'atan2', 'fmod', 'hypot', 'pow']
 
 for name, args, res in complex_math_functions:
     func = getattr(math, name)

Modified: pypy/trunk/pypy/rpython/lltypesystem/module/ll_math.py
==============================================================================
--- pypy/trunk/pypy/rpython/lltypesystem/module/ll_math.py	(original)
+++ pypy/trunk/pypy/rpython/lltypesystem/module/ll_math.py	Tue Mar 16 16:50:42 2010
@@ -7,108 +7,308 @@
 from pypy.tool.sourcetools import func_with_new_name
 from pypy.rlib import rposix
 from pypy.translator.tool.cbuild import ExternalCompilationInfo
-from pypy.rlib.rarithmetic import isinf
+from pypy.rlib.rarithmetic import isinf, isnan, INFINITY, NAN
 
-math_frexp = rffi.llexternal('frexp', [rffi.DOUBLE, rffi.INTP], rffi.DOUBLE,
-                             sandboxsafe=True)
-math_modf  = rffi.llexternal('modf',  [rffi.DOUBLE, rffi.DOUBLEP], rffi.DOUBLE,
-                             sandboxsafe=True)
-math_ldexp = rffi.llexternal('ldexp', [rffi.DOUBLE, rffi.INT], rffi.DOUBLE,
-                             sandboxsafe=True)
+if sys.platform[:3] == "win":
+    eci = ExternalCompilationInfo(libraries=[])
+else:
+    eci = ExternalCompilationInfo(libraries=['m'])
 
-unary_math_functions = [
-    'acos', 'asin', 'atan', 'ceil', 'cos', 'cosh', 'exp', 'fabs',
-    'floor', 'log', 'log10', 'sin', 'sinh', 'sqrt', 'tan', 'tanh'
-    ]
+def llexternal(name, ARGS, RESULT):
+    return rffi.llexternal(name, ARGS, RESULT, compilation_info=eci,
+                           sandboxsafe=True)
+
+if sys.platform == 'win32':
+    underscore = '_'
+else:
+    underscore = ''
+
+math_fabs = llexternal('fabs', [rffi.DOUBLE], rffi.DOUBLE)
+math_log = llexternal('log', [rffi.DOUBLE], rffi.DOUBLE)
+math_log10 = llexternal('log10', [rffi.DOUBLE], rffi.DOUBLE)
+math_copysign = llexternal(underscore + 'copysign',
+                           [rffi.DOUBLE, rffi.DOUBLE], rffi.DOUBLE)
+math_atan2 = llexternal('atan2', [rffi.DOUBLE, rffi.DOUBLE], rffi.DOUBLE)
+math_frexp = llexternal('frexp', [rffi.DOUBLE, rffi.INTP], rffi.DOUBLE)
+math_modf  = llexternal('modf',  [rffi.DOUBLE, rffi.DOUBLEP], rffi.DOUBLE)
+math_ldexp = llexternal('ldexp', [rffi.DOUBLE, rffi.INT], rffi.DOUBLE)
+math_pow   = llexternal('pow', [rffi.DOUBLE, rffi.DOUBLE], rffi.DOUBLE)
+math_fmod  = llexternal('fmod',  [rffi.DOUBLE, rffi.DOUBLE], rffi.DOUBLE)
+math_hypot = llexternal(underscore + 'hypot',
+                        [rffi.DOUBLE, rffi.DOUBLE], rffi.DOUBLE)
+
+# ____________________________________________________________
+#
+# Error handling functions
+
+ERANGE = errno.ERANGE
+EDOM   = errno.EDOM
+
+def _error_reset():
+    rposix.set_errno(0)
+
+def _likely_raise(errno, x):
+    """Call this with errno != 0.  It usually raises the proper RPython
+    exception, but may also just ignore it and return in case of underflow.
+    """
+    assert errno
+    if errno == ERANGE:
+        # We consider underflow to not be an error, like CPython.
+        # On some platforms (Ubuntu/ia64) it seems that errno can be
+        # set to ERANGE for subnormal results that do *not* underflow
+        # to zero.  So to be safe, we'll ignore ERANGE whenever the
+        # function result is less than one in absolute value.
+        if math_fabs(x) < 1.0:
+            return
+        raise OverflowError("math range error")
+    else:
+        raise ValueError("math domain error")
+
+# ____________________________________________________________
+#
+# Custom implementations
+
+
+def ll_math_atan2(y, x):
+    """wrapper for atan2 that deals directly with special cases before
+    delegating to the platform libm for the remaining cases.  This
+    is necessary to get consistent behaviour across platforms.
+    Windows, FreeBSD and alpha Tru64 are amongst platforms that don't
+    always follow C99.
+    """
+    if isnan(x) or isnan(y):
+        return NAN
+
+    if isinf(y):
+        if isinf(x):
+            if math_copysign(1.0, x) == 1.0:
+                # atan2(+-inf, +inf) == +-pi/4
+                return math_copysign(0.25 * math.pi, y)
+            else:
+                # atan2(+-inf, -inf) == +-pi*3/4
+                return math_copysign(0.75 * math.pi, y)
+        # atan2(+-inf, x) == +-pi/2 for finite x
+        return math_copysign(0.5 * math.pi, y)
+
+    if isinf(x) or y == 0.0:
+        if math_copysign(1.0, x) == 1.0:
+            # atan2(+-y, +inf) = atan2(+-0, +x) = +-0.
+            return math_copysign(0.0, y)
+        else:
+            # atan2(+-y, -inf) = atan2(+-0., -x) = +-pi.
+            return math_copysign(math.pi, y)
+
+    return math_atan2(y, x)
+
+
+# XXX Various platforms (Solaris, OpenBSD) do nonstandard things for log(0),
+# log(-ve), log(NaN).  For now I'm ignoring this issue as these are a bit
+# more marginal platforms for us.
 
-binary_math_functions = [
-    'atan2', 'fmod', 'hypot', 'pow'
-    ]
 
 def ll_math_frexp(x):
-    exp_p = lltype.malloc(rffi.INTP.TO, 1, flavor='raw')
-    try:
-        _error_reset()
-        mantissa = math_frexp(x, exp_p)
-        _check_error(mantissa)
-        exponent = rffi.cast(lltype.Signed, exp_p[0])
-    finally:
-        lltype.free(exp_p, flavor='raw')
+    # deal with special cases directly, to sidestep platform differences
+    if isnan(x) or isinf(x) or not x:
+        mantissa = x
+        exponent = 0
+    else:
+        exp_p = lltype.malloc(rffi.INTP.TO, 1, flavor='raw')
+        try:
+            mantissa = math_frexp(x, exp_p)
+            exponent = rffi.cast(lltype.Signed, exp_p[0])
+        finally:
+            lltype.free(exp_p, flavor='raw')
     return (mantissa, exponent)
 
+
+INT_MAX = int(2**31-1)
+INT_MIN = int(-2**31)
+
+def ll_math_ldexp(x, exp):
+    if x == 0.0 or isinf(x) or isnan(x):
+        return x    # NaNs, zeros and infinities are returned unchanged
+    if exp > INT_MAX:
+        # overflow (64-bit platforms only)
+        r = math_copysign(INFINITY, x)
+        errno = ERANGE
+    elif exp < INT_MIN:
+        # underflow to +-0 (64-bit platforms only)
+        r = math_copysign(0.0, x)
+        errno = 0
+    else:
+        _error_reset()
+        r = math_ldexp(x, exp)
+        errno = rposix.get_errno()
+        if isinf(r):
+            errno = ERANGE
+    if errno:
+        _likely_raise(errno, r)
+    return r
+
+
 def ll_math_modf(x):
+    # some platforms don't do the right thing for NaNs and
+    # infinities, so we take care of special cases directly.
+    if isinf(x):
+        return (math_copysign(0.0, x), x)
+    elif isnan(x):
+        return (x, x)
     intpart_p = lltype.malloc(rffi.DOUBLEP.TO, 1, flavor='raw')
     try:
-        _error_reset()
         fracpart = math_modf(x, intpart_p)
-        _check_error(fracpart)
         intpart = intpart_p[0]
     finally:
         lltype.free(intpart_p, flavor='raw')
     return (fracpart, intpart)
 
-def ll_math_ldexp(x, exp):
+
+def ll_math_copysign(x, y):
+    return math_copysign(x, y)     # no error checking needed
+
+
+def ll_math_fmod(x, y):
+    if isinf(y):
+        if isinf(x):
+            raise ValueError("math domain error")
+        return x  # fmod(x, +/-Inf) returns x for finite x (or if x is a NaN).
+
     _error_reset()
-    r = math_ldexp(x, exp)
-    _check_error(r)
+    r = math_fmod(x, y)
+    errno = rposix.get_errno()
+    if isnan(r):
+        if isnan(x) or isnan(y):
+            errno = 0
+        else:
+            errno = EDOM
+    if errno:
+        _likely_raise(errno, r)
     return r
 
-def _error_reset():
-    rposix.set_errno(0)
 
-ERANGE = errno.ERANGE
-def _check_error(x):
-    errno = rposix.get_errno()
+def ll_math_hypot(x, y):
+    # hypot(x, +/-Inf) returns Inf, even if x is a NaN.
     if isinf(x):
-        errno = ERANGE
+        return math_fabs(x)
+    if isinf(y):
+        return math_fabs(y)
+
+    _error_reset()
+    r = math_hypot(x, y)
+    errno = rposix.get_errno()
+    if isnan(r):
+        if isnan(x) or isnan(y):
+            errno = 0
+        else:
+            errno = EDOM
+    elif isinf(r):
+        if isinf(x) or isnan(x) or isinf(y) or isnan(y):
+            errno = 0
+        else:
+            errno = ERANGE
     if errno:
-        if errno == ERANGE:
-            if not x:
-                return # we consider underflow to not be an error, like CPython
-            raise OverflowError("math range error")
+        _likely_raise(errno, r)
+    return r
+
+
+def ll_math_pow(x, y):
+    # deal directly with IEEE specials, to cope with problems on various
+    # platforms whose semantics don't exactly match C99
+
+    if isnan(x):
+        if y == 0.0:
+            return 1.0   # NaN**0 = 1
+        return x
+
+    elif isnan(y):
+        if x == 1.0:
+            return 1.0   # 1**Nan = 1
+        return y
+
+    elif isinf(x):
+        odd_y = not isinf(y) and math_fmod(math_fabs(y), 2.0) == 1.0
+        if y > 0.0:
+            if odd_y:
+                return x
+            return math_fabs(x)
+        elif y == 0.0:
+            return 1.0
+        else:   # y < 0.0
+            if odd_y:
+                return math_copysign(0.0, x)
+            return 0.0
+
+    elif isinf(y):
+        if math_fabs(x) == 1.0:
+            return 1.0
+        elif y > 0.0 and math_fabs(x) > 1.0:
+            return y
+        elif y < 0.0 and math_fabs(x) < 1.0:
+            if x == 0.0:
+                raise ValueError("0**-inf: divide by zero")
+            return -y    # result is +inf
         else:
-            raise ValueError("math domain error")
+            return 0.0
 
-if sys.platform[:3] == "win":
-    eci = ExternalCompilationInfo(libraries=[])
-else:
-    eci = ExternalCompilationInfo(libraries=['m'])
+    _error_reset()
+    r = math_pow(x, y)
+    errno = rposix.get_errno()
+    if isnan(r):
+        # a NaN result should arise only from (-ve)**(finite non-integer)
+        errno = EDOM
+    elif isinf(r):
+        # an infinite result here arises either from:
+        # (A) (+/-0.)**negative (-> divide-by-zero)
+        # (B) overflow of x**y with x and y finite
+        if x == 0.0:
+            errno = EDOM
+        else:
+            errno = ERANGE
+    if errno:
+        _likely_raise(errno, r)
+    return r
 
+# ____________________________________________________________
+#
+# Default implementations
 
-def new_unary_math_function(name):
-    c_func = rffi.llexternal(name, [rffi.DOUBLE], rffi.DOUBLE,
-                             compilation_info=eci, sandboxsafe=True)
+def new_unary_math_function(name, can_overflow):
+    c_func = llexternal(name, [rffi.DOUBLE], rffi.DOUBLE)
 
     def ll_math(x):
         _error_reset()
         r = c_func(x)
-        _check_error(r)
+        # Error checking fun.  Copied from CPython 2.6
+        errno = rposix.get_errno()
+        if isnan(r):
+            if isnan(x):
+                errno = 0
+            else:
+                errno = EDOM
+        elif isinf(r):
+            if isinf(x) or isnan(x):
+                errno = 0
+            elif can_overflow:
+                errno = ERANGE
+            else:
+                errno = EDOM
+        if errno:
+            _likely_raise(errno, r)
         return r
 
     return func_with_new_name(ll_math, 'll_math_' + name)
 
-def new_binary_math_function(name):
-    if sys.platform == 'win32' and name in ('hypot',):
-        cname = '_' + name
-    else:
-        cname = name
-    c_func = rffi.llexternal(cname, [rffi.DOUBLE, rffi.DOUBLE], rffi.DOUBLE,
-                             compilation_info=eci, sandboxsafe=True)
+# ____________________________________________________________
 
-    def ll_math(x, y):
-        _error_reset()
-        r = c_func(x, y)
-        _check_error(r)
-        return r
-
-    return func_with_new_name(ll_math, 'll_math_' + name)
-
-# the two above are almost the same, but they're C-c C-v not to go mad
-# with meta-programming
+unary_math_functions = [
+    'acos', 'asin', 'atan',
+    'ceil', 'cos', 'cosh', 'exp', 'fabs', 'floor',
+    'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'log', 'log10',
+    # 'log1p', 'acosh', 'asinh', 'atanh',   -- added in Python 2.6
+    ]
+unary_math_functions_can_overflow = [
+    'cosh', 'exp', 'log1p', 'sinh', # why log1p? CPython does it
+    ]
 
 for name in unary_math_functions:
-    globals()['ll_math_' + name] = new_unary_math_function(name)
-    
-for name in binary_math_functions:
-    globals()['ll_math_' + name] = new_binary_math_function(name)
-    
+    can_overflow = name in unary_math_functions_can_overflow
+    globals()['ll_math_' + name] = new_unary_math_function(name, can_overflow)



More information about the Pypy-commit mailing list