Back to index

python3.2  3.2.2
test_cmath.py
Go to the documentation of this file.
00001 from test.support import run_unittest, requires_IEEE_754
00002 from test.test_math import parse_testfile, test_file
00003 import unittest
00004 import cmath, math
00005 from cmath import phase, polar, rect, pi
00006 import sysconfig
00007 
00008 INF = float('inf')
00009 NAN = float('nan')
00010 
00011 complex_zeros = [complex(x, y) for x in [0.0, -0.0] for y in [0.0, -0.0]]
00012 complex_infinities = [complex(x, y) for x, y in [
00013         (INF, 0.0),  # 1st quadrant
00014         (INF, 2.3),
00015         (INF, INF),
00016         (2.3, INF),
00017         (0.0, INF),
00018         (-0.0, INF), # 2nd quadrant
00019         (-2.3, INF),
00020         (-INF, INF),
00021         (-INF, 2.3),
00022         (-INF, 0.0),
00023         (-INF, -0.0), # 3rd quadrant
00024         (-INF, -2.3),
00025         (-INF, -INF),
00026         (-2.3, -INF),
00027         (-0.0, -INF),
00028         (0.0, -INF), # 4th quadrant
00029         (2.3, -INF),
00030         (INF, -INF),
00031         (INF, -2.3),
00032         (INF, -0.0)
00033         ]]
00034 complex_nans = [complex(x, y) for x, y in [
00035         (NAN, -INF),
00036         (NAN, -2.3),
00037         (NAN, -0.0),
00038         (NAN, 0.0),
00039         (NAN, 2.3),
00040         (NAN, INF),
00041         (-INF, NAN),
00042         (-2.3, NAN),
00043         (-0.0, NAN),
00044         (0.0, NAN),
00045         (2.3, NAN),
00046         (INF, NAN)
00047         ]]
00048 
00049 class CMathTests(unittest.TestCase):
00050     # list of all functions in cmath
00051     test_functions = [getattr(cmath, fname) for fname in [
00052             'acos', 'acosh', 'asin', 'asinh', 'atan', 'atanh',
00053             'cos', 'cosh', 'exp', 'log', 'log10', 'sin', 'sinh',
00054             'sqrt', 'tan', 'tanh']]
00055     # test first and second arguments independently for 2-argument log
00056     test_functions.append(lambda x : cmath.log(x, 1729. + 0j))
00057     test_functions.append(lambda x : cmath.log(14.-27j, x))
00058 
00059     def setUp(self):
00060         self.test_values = open(test_file)
00061 
00062     def tearDown(self):
00063         self.test_values.close()
00064 
00065     def assertFloatIdentical(self, x, y):
00066         """Fail unless floats x and y are identical, in the sense that:
00067         (1) both x and y are nans, or
00068         (2) both x and y are infinities, with the same sign, or
00069         (3) both x and y are zeros, with the same sign, or
00070         (4) x and y are both finite and nonzero, and x == y
00071 
00072         """
00073         msg = 'floats {!r} and {!r} are not identical'
00074 
00075         if math.isnan(x) or math.isnan(y):
00076             if math.isnan(x) and math.isnan(y):
00077                 return
00078         elif x == y:
00079             if x != 0.0:
00080                 return
00081             # both zero; check that signs match
00082             elif math.copysign(1.0, x) == math.copysign(1.0, y):
00083                 return
00084             else:
00085                 msg += ': zeros have different signs'
00086         self.fail(msg.format(x, y))
00087 
00088     def assertComplexIdentical(self, x, y):
00089         """Fail unless complex numbers x and y have equal values and signs.
00090 
00091         In particular, if x and y both have real (or imaginary) part
00092         zero, but the zeros have different signs, this test will fail.
00093 
00094         """
00095         self.assertFloatIdentical(x.real, y.real)
00096         self.assertFloatIdentical(x.imag, y.imag)
00097 
00098     def rAssertAlmostEqual(self, a, b, rel_err = 2e-15, abs_err = 5e-323,
00099                            msg=None):
00100         """Fail if the two floating-point numbers are not almost equal.
00101 
00102         Determine whether floating-point values a and b are equal to within
00103         a (small) rounding error.  The default values for rel_err and
00104         abs_err are chosen to be suitable for platforms where a float is
00105         represented by an IEEE 754 double.  They allow an error of between
00106         9 and 19 ulps.
00107         """
00108 
00109         # special values testing
00110         if math.isnan(a):
00111             if math.isnan(b):
00112                 return
00113             self.fail(msg or '{!r} should be nan'.format(b))
00114 
00115         if math.isinf(a):
00116             if a == b:
00117                 return
00118             self.fail(msg or 'finite result where infinity expected: '
00119                       'expected {!r}, got {!r}'.format(a, b))
00120 
00121         # if both a and b are zero, check whether they have the same sign
00122         # (in theory there are examples where it would be legitimate for a
00123         # and b to have opposite signs; in practice these hardly ever
00124         # occur).
00125         if not a and not b:
00126             if math.copysign(1., a) != math.copysign(1., b):
00127                 self.fail(msg or 'zero has wrong sign: expected {!r}, '
00128                           'got {!r}'.format(a, b))
00129 
00130         # if a-b overflows, or b is infinite, return False.  Again, in
00131         # theory there are examples where a is within a few ulps of the
00132         # max representable float, and then b could legitimately be
00133         # infinite.  In practice these examples are rare.
00134         try:
00135             absolute_error = abs(b-a)
00136         except OverflowError:
00137             pass
00138         else:
00139             # test passes if either the absolute error or the relative
00140             # error is sufficiently small.  The defaults amount to an
00141             # error of between 9 ulps and 19 ulps on an IEEE-754 compliant
00142             # machine.
00143             if absolute_error <= max(abs_err, rel_err * abs(a)):
00144                 return
00145         self.fail(msg or
00146                   '{!r} and {!r} are not sufficiently close'.format(a, b))
00147 
00148     def test_constants(self):
00149         e_expected = 2.71828182845904523536
00150         pi_expected = 3.14159265358979323846
00151         self.assertAlmostEqual(cmath.pi, pi_expected, places=9,
00152             msg="cmath.pi is {}; should be {}".format(cmath.pi, pi_expected))
00153         self.assertAlmostEqual(cmath.e, e_expected, places=9,
00154             msg="cmath.e is {}; should be {}".format(cmath.e, e_expected))
00155 
00156     def test_user_object(self):
00157         # Test automatic calling of __complex__ and __float__ by cmath
00158         # functions
00159 
00160         # some random values to use as test values; we avoid values
00161         # for which any of the functions in cmath is undefined
00162         # (i.e. 0., 1., -1., 1j, -1j) or would cause overflow
00163         cx_arg = 4.419414439 + 1.497100113j
00164         flt_arg = -6.131677725
00165 
00166         # a variety of non-complex numbers, used to check that
00167         # non-complex return values from __complex__ give an error
00168         non_complexes = ["not complex", 1, 5, 2., None,
00169                          object(), NotImplemented]
00170 
00171         # Now we introduce a variety of classes whose instances might
00172         # end up being passed to the cmath functions
00173 
00174         # usual case: new-style class implementing __complex__
00175         class MyComplex(object):
00176             def __init__(self, value):
00177                 self.value = value
00178             def __complex__(self):
00179                 return self.value
00180 
00181         # old-style class implementing __complex__
00182         class MyComplexOS:
00183             def __init__(self, value):
00184                 self.value = value
00185             def __complex__(self):
00186                 return self.value
00187 
00188         # classes for which __complex__ raises an exception
00189         class SomeException(Exception):
00190             pass
00191         class MyComplexException(object):
00192             def __complex__(self):
00193                 raise SomeException
00194         class MyComplexExceptionOS:
00195             def __complex__(self):
00196                 raise SomeException
00197 
00198         # some classes not providing __float__ or __complex__
00199         class NeitherComplexNorFloat(object):
00200             pass
00201         class NeitherComplexNorFloatOS:
00202             pass
00203         class MyInt(object):
00204             def __int__(self): return 2
00205             def __index__(self): return 2
00206         class MyIntOS:
00207             def __int__(self): return 2
00208             def __index__(self): return 2
00209 
00210         # other possible combinations of __float__ and __complex__
00211         # that should work
00212         class FloatAndComplex(object):
00213             def __float__(self):
00214                 return flt_arg
00215             def __complex__(self):
00216                 return cx_arg
00217         class FloatAndComplexOS:
00218             def __float__(self):
00219                 return flt_arg
00220             def __complex__(self):
00221                 return cx_arg
00222         class JustFloat(object):
00223             def __float__(self):
00224                 return flt_arg
00225         class JustFloatOS:
00226             def __float__(self):
00227                 return flt_arg
00228 
00229         for f in self.test_functions:
00230             # usual usage
00231             self.assertEqual(f(MyComplex(cx_arg)), f(cx_arg))
00232             self.assertEqual(f(MyComplexOS(cx_arg)), f(cx_arg))
00233             # other combinations of __float__ and __complex__
00234             self.assertEqual(f(FloatAndComplex()), f(cx_arg))
00235             self.assertEqual(f(FloatAndComplexOS()), f(cx_arg))
00236             self.assertEqual(f(JustFloat()), f(flt_arg))
00237             self.assertEqual(f(JustFloatOS()), f(flt_arg))
00238             # TypeError should be raised for classes not providing
00239             # either __complex__ or __float__, even if they provide
00240             # __int__ or __index__.  An old-style class
00241             # currently raises AttributeError instead of a TypeError;
00242             # this could be considered a bug.
00243             self.assertRaises(TypeError, f, NeitherComplexNorFloat())
00244             self.assertRaises(TypeError, f, MyInt())
00245             self.assertRaises(Exception, f, NeitherComplexNorFloatOS())
00246             self.assertRaises(Exception, f, MyIntOS())
00247             # non-complex return value from __complex__ -> TypeError
00248             for bad_complex in non_complexes:
00249                 self.assertRaises(TypeError, f, MyComplex(bad_complex))
00250                 self.assertRaises(TypeError, f, MyComplexOS(bad_complex))
00251             # exceptions in __complex__ should be propagated correctly
00252             self.assertRaises(SomeException, f, MyComplexException())
00253             self.assertRaises(SomeException, f, MyComplexExceptionOS())
00254 
00255     def test_input_type(self):
00256         # ints and longs should be acceptable inputs to all cmath
00257         # functions, by virtue of providing a __float__ method
00258         for f in self.test_functions:
00259             for arg in [2, 2.]:
00260                 self.assertEqual(f(arg), f(arg.__float__()))
00261 
00262         # but strings should give a TypeError
00263         for f in self.test_functions:
00264             for arg in ["a", "long_string", "0", "1j", ""]:
00265                 self.assertRaises(TypeError, f, arg)
00266 
00267     def test_cmath_matches_math(self):
00268         # check that corresponding cmath and math functions are equal
00269         # for floats in the appropriate range
00270 
00271         # test_values in (0, 1)
00272         test_values = [0.01, 0.1, 0.2, 0.5, 0.9, 0.99]
00273 
00274         # test_values for functions defined on [-1., 1.]
00275         unit_interval = test_values + [-x for x in test_values] + \
00276             [0., 1., -1.]
00277 
00278         # test_values for log, log10, sqrt
00279         positive = test_values + [1.] + [1./x for x in test_values]
00280         nonnegative = [0.] + positive
00281 
00282         # test_values for functions defined on the whole real line
00283         real_line = [0.] + positive + [-x for x in positive]
00284 
00285         test_functions = {
00286             'acos' : unit_interval,
00287             'asin' : unit_interval,
00288             'atan' : real_line,
00289             'cos' : real_line,
00290             'cosh' : real_line,
00291             'exp' : real_line,
00292             'log' : positive,
00293             'log10' : positive,
00294             'sin' : real_line,
00295             'sinh' : real_line,
00296             'sqrt' : nonnegative,
00297             'tan' : real_line,
00298             'tanh' : real_line}
00299 
00300         for fn, values in test_functions.items():
00301             float_fn = getattr(math, fn)
00302             complex_fn = getattr(cmath, fn)
00303             for v in values:
00304                 z = complex_fn(v)
00305                 self.rAssertAlmostEqual(float_fn(v), z.real)
00306                 self.assertEqual(0., z.imag)
00307 
00308         # test two-argument version of log with various bases
00309         for base in [0.5, 2., 10.]:
00310             for v in positive:
00311                 z = cmath.log(v, base)
00312                 self.rAssertAlmostEqual(math.log(v, base), z.real)
00313                 self.assertEqual(0., z.imag)
00314 
00315     @requires_IEEE_754
00316     def test_specific_values(self):
00317         def rect_complex(z):
00318             """Wrapped version of rect that accepts a complex number instead of
00319             two float arguments."""
00320             return cmath.rect(z.real, z.imag)
00321 
00322         def polar_complex(z):
00323             """Wrapped version of polar that returns a complex number instead of
00324             two floats."""
00325             return complex(*polar(z))
00326 
00327         for id, fn, ar, ai, er, ei, flags in parse_testfile(test_file):
00328             arg = complex(ar, ai)
00329             expected = complex(er, ei)
00330             if fn == 'rect':
00331                 function = rect_complex
00332             elif fn == 'polar':
00333                 function = polar_complex
00334             else:
00335                 function = getattr(cmath, fn)
00336             if 'divide-by-zero' in flags or 'invalid' in flags:
00337                 try:
00338                     actual = function(arg)
00339                 except ValueError:
00340                     continue
00341                 else:
00342                     self.fail('ValueError not raised in test '
00343                           '{}: {}(complex({!r}, {!r}))'.format(id, fn, ar, ai))
00344 
00345             if 'overflow' in flags:
00346                 try:
00347                     actual = function(arg)
00348                 except OverflowError:
00349                     continue
00350                 else:
00351                     self.fail('OverflowError not raised in test '
00352                           '{}: {}(complex({!r}, {!r}))'.format(id, fn, ar, ai))
00353 
00354             actual = function(arg)
00355 
00356             if 'ignore-real-sign' in flags:
00357                 actual = complex(abs(actual.real), actual.imag)
00358                 expected = complex(abs(expected.real), expected.imag)
00359             if 'ignore-imag-sign' in flags:
00360                 actual = complex(actual.real, abs(actual.imag))
00361                 expected = complex(expected.real, abs(expected.imag))
00362 
00363             # for the real part of the log function, we allow an
00364             # absolute error of up to 2e-15.
00365             if fn in ('log', 'log10'):
00366                 real_abs_err = 2e-15
00367             else:
00368                 real_abs_err = 5e-323
00369 
00370             error_message = (
00371                 '{}: {}(complex({!r}, {!r}))\n'
00372                 'Expected: complex({!r}, {!r})\n'
00373                 'Received: complex({!r}, {!r})\n'
00374                 'Received value insufficiently close to expected value.'
00375                 ).format(id, fn, ar, ai,
00376                      expected.real, expected.imag,
00377                      actual.real, actual.imag)
00378             self.rAssertAlmostEqual(expected.real, actual.real,
00379                                         abs_err=real_abs_err,
00380                                         msg=error_message)
00381             self.rAssertAlmostEqual(expected.imag, actual.imag,
00382                                         msg=error_message)
00383 
00384     def assertCISEqual(self, a, b):
00385         eps = 1E-7
00386         if abs(a[0] - b[0]) > eps or abs(a[1] - b[1]) > eps:
00387             self.fail((a ,b))
00388 
00389     def test_polar(self):
00390         self.assertCISEqual(polar(0), (0., 0.))
00391         self.assertCISEqual(polar(1.), (1., 0.))
00392         self.assertCISEqual(polar(-1.), (1., pi))
00393         self.assertCISEqual(polar(1j), (1., pi/2))
00394         self.assertCISEqual(polar(-1j), (1., -pi/2))
00395 
00396     def test_phase(self):
00397         self.assertAlmostEqual(phase(0), 0.)
00398         self.assertAlmostEqual(phase(1.), 0.)
00399         self.assertAlmostEqual(phase(-1.), pi)
00400         self.assertAlmostEqual(phase(-1.+1E-300j), pi)
00401         self.assertAlmostEqual(phase(-1.-1E-300j), -pi)
00402         self.assertAlmostEqual(phase(1j), pi/2)
00403         self.assertAlmostEqual(phase(-1j), -pi/2)
00404 
00405         # zeros
00406         self.assertEqual(phase(complex(0.0, 0.0)), 0.0)
00407         self.assertEqual(phase(complex(0.0, -0.0)), -0.0)
00408         self.assertEqual(phase(complex(-0.0, 0.0)), pi)
00409         self.assertEqual(phase(complex(-0.0, -0.0)), -pi)
00410 
00411         # infinities
00412         self.assertAlmostEqual(phase(complex(-INF, -0.0)), -pi)
00413         self.assertAlmostEqual(phase(complex(-INF, -2.3)), -pi)
00414         self.assertAlmostEqual(phase(complex(-INF, -INF)), -0.75*pi)
00415         self.assertAlmostEqual(phase(complex(-2.3, -INF)), -pi/2)
00416         self.assertAlmostEqual(phase(complex(-0.0, -INF)), -pi/2)
00417         self.assertAlmostEqual(phase(complex(0.0, -INF)), -pi/2)
00418         self.assertAlmostEqual(phase(complex(2.3, -INF)), -pi/2)
00419         self.assertAlmostEqual(phase(complex(INF, -INF)), -pi/4)
00420         self.assertEqual(phase(complex(INF, -2.3)), -0.0)
00421         self.assertEqual(phase(complex(INF, -0.0)), -0.0)
00422         self.assertEqual(phase(complex(INF, 0.0)), 0.0)
00423         self.assertEqual(phase(complex(INF, 2.3)), 0.0)
00424         self.assertAlmostEqual(phase(complex(INF, INF)), pi/4)
00425         self.assertAlmostEqual(phase(complex(2.3, INF)), pi/2)
00426         self.assertAlmostEqual(phase(complex(0.0, INF)), pi/2)
00427         self.assertAlmostEqual(phase(complex(-0.0, INF)), pi/2)
00428         self.assertAlmostEqual(phase(complex(-2.3, INF)), pi/2)
00429         self.assertAlmostEqual(phase(complex(-INF, INF)), 0.75*pi)
00430         self.assertAlmostEqual(phase(complex(-INF, 2.3)), pi)
00431         self.assertAlmostEqual(phase(complex(-INF, 0.0)), pi)
00432 
00433         # real or imaginary part NaN
00434         for z in complex_nans:
00435             self.assertTrue(math.isnan(phase(z)))
00436 
00437     def test_abs(self):
00438         # zeros
00439         for z in complex_zeros:
00440             self.assertEqual(abs(z), 0.0)
00441 
00442         # infinities
00443         for z in complex_infinities:
00444             self.assertEqual(abs(z), INF)
00445 
00446         # real or imaginary part NaN
00447         self.assertEqual(abs(complex(NAN, -INF)), INF)
00448         self.assertTrue(math.isnan(abs(complex(NAN, -2.3))))
00449         self.assertTrue(math.isnan(abs(complex(NAN, -0.0))))
00450         self.assertTrue(math.isnan(abs(complex(NAN, 0.0))))
00451         self.assertTrue(math.isnan(abs(complex(NAN, 2.3))))
00452         self.assertEqual(abs(complex(NAN, INF)), INF)
00453         self.assertEqual(abs(complex(-INF, NAN)), INF)
00454         self.assertTrue(math.isnan(abs(complex(-2.3, NAN))))
00455         self.assertTrue(math.isnan(abs(complex(-0.0, NAN))))
00456         self.assertTrue(math.isnan(abs(complex(0.0, NAN))))
00457         self.assertTrue(math.isnan(abs(complex(2.3, NAN))))
00458         self.assertEqual(abs(complex(INF, NAN)), INF)
00459         self.assertTrue(math.isnan(abs(complex(NAN, NAN))))
00460 
00461 
00462     @requires_IEEE_754
00463     def test_abs_overflows(self):
00464         # result overflows
00465         self.assertRaises(OverflowError, abs, complex(1.4e308, 1.4e308))
00466 
00467     def assertCEqual(self, a, b):
00468         eps = 1E-7
00469         if abs(a.real - b[0]) > eps or abs(a.imag - b[1]) > eps:
00470             self.fail((a ,b))
00471 
00472     def test_rect(self):
00473         self.assertCEqual(rect(0, 0), (0, 0))
00474         self.assertCEqual(rect(1, 0), (1., 0))
00475         self.assertCEqual(rect(1, -pi), (-1., 0))
00476         self.assertCEqual(rect(1, pi/2), (0, 1.))
00477         self.assertCEqual(rect(1, -pi/2), (0, -1.))
00478 
00479     def test_isfinite(self):
00480         real_vals = [float('-inf'), -2.3, -0.0,
00481                      0.0, 2.3, float('inf'), float('nan')]
00482         for x in real_vals:
00483             for y in real_vals:
00484                 z = complex(x, y)
00485                 self.assertEqual(cmath.isfinite(z),
00486                                   math.isfinite(x) and math.isfinite(y))
00487 
00488     def test_isnan(self):
00489         self.assertFalse(cmath.isnan(1))
00490         self.assertFalse(cmath.isnan(1j))
00491         self.assertFalse(cmath.isnan(INF))
00492         self.assertTrue(cmath.isnan(NAN))
00493         self.assertTrue(cmath.isnan(complex(NAN, 0)))
00494         self.assertTrue(cmath.isnan(complex(0, NAN)))
00495         self.assertTrue(cmath.isnan(complex(NAN, NAN)))
00496         self.assertTrue(cmath.isnan(complex(NAN, INF)))
00497         self.assertTrue(cmath.isnan(complex(INF, NAN)))
00498 
00499     def test_isinf(self):
00500         self.assertFalse(cmath.isinf(1))
00501         self.assertFalse(cmath.isinf(1j))
00502         self.assertFalse(cmath.isinf(NAN))
00503         self.assertTrue(cmath.isinf(INF))
00504         self.assertTrue(cmath.isinf(complex(INF, 0)))
00505         self.assertTrue(cmath.isinf(complex(0, INF)))
00506         self.assertTrue(cmath.isinf(complex(INF, INF)))
00507         self.assertTrue(cmath.isinf(complex(NAN, INF)))
00508         self.assertTrue(cmath.isinf(complex(INF, NAN)))
00509 
00510     @requires_IEEE_754
00511     @unittest.skipIf(sysconfig.get_config_var('TANH_PRESERVES_ZERO_SIGN') == 0,
00512                      "system tanh() function doesn't copy the sign")
00513     def testTanhSign(self):
00514         for z in complex_zeros:
00515             self.assertComplexIdentical(cmath.tanh(z), z)
00516 
00517     # The algorithm used for atan and atanh makes use of the system
00518     # log1p function; If that system function doesn't respect the sign
00519     # of zero, then atan and atanh will also have difficulties with
00520     # the sign of complex zeros.
00521     @requires_IEEE_754
00522     @unittest.skipIf(sysconfig.get_config_var('LOG1P_DROPS_ZERO_SIGN'),
00523                      "system log1p() function doesn't preserve the sign")
00524     def testAtanSign(self):
00525         for z in complex_zeros:
00526             self.assertComplexIdentical(cmath.atan(z), z)
00527 
00528     @requires_IEEE_754
00529     @unittest.skipIf(sysconfig.get_config_var('LOG1P_DROPS_ZERO_SIGN'),
00530                      "system log1p() function doesn't preserve the sign")
00531     def testAtanhSign(self):
00532         for z in complex_zeros:
00533             self.assertComplexIdentical(cmath.atanh(z), z)
00534 
00535 
00536 def test_main():
00537     run_unittest(CMathTests)
00538 
00539 if __name__ == "__main__":
00540     test_main()