PageRenderTime 33ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/pypy/module/math/test/test_math.py

https://bitbucket.org/dac_io/pypy
Python | 275 lines | 232 code | 19 blank | 24 comment | 28 complexity | 4683d753215962c643feb376b5fd5113 MD5 | raw file
  1. from __future__ import with_statement
  2. import sys
  3. from pypy.conftest import gettestobjspace
  4. from pypy.module.math.test import test_direct
  5. class AppTestMath:
  6. def setup_class(cls):
  7. cls.space = gettestobjspace(usemodules=['math', 'struct'])
  8. cls.w_cases = cls.space.wrap(test_direct.MathTests.TESTCASES)
  9. cls.w_consistent_host = cls.space.wrap(test_direct.consistent_host)
  10. def w_ftest(self, actual, expected):
  11. assert abs(actual - expected) < 10E-5
  12. def test_all_cases(self):
  13. if not self.consistent_host:
  14. skip("please test this on top of PyPy or CPython >= 2.6")
  15. import math
  16. for fnname, args, expected in self.cases:
  17. fn = getattr(math, fnname)
  18. print fn, args
  19. try:
  20. got = fn(*args)
  21. except ValueError:
  22. assert expected == ValueError
  23. except OverflowError:
  24. assert expected == OverflowError
  25. else:
  26. if type(expected) is type(Exception):
  27. ok = False
  28. elif callable(expected):
  29. ok = expected(got)
  30. else:
  31. gotsign = expectedsign = 1
  32. if got < 0.0: gotsign = -gotsign
  33. if expected < 0.0: expectedsign = -expectedsign
  34. ok = got == expected and gotsign == expectedsign
  35. if not ok:
  36. raise AssertionError("%s(%s): got %s" % (
  37. fnname, ', '.join(map(str, args)), got))
  38. def test_ldexp(self):
  39. import math
  40. assert math.ldexp(float("inf"), -10**20) == float("inf")
  41. def test_fsum(self):
  42. import math
  43. # detect evidence of double-rounding: fsum is not always correctly
  44. # rounded on machines that suffer from double rounding.
  45. # It is a known problem with IA32 floating-point arithmetic.
  46. # It should work fine e.g. with x86-64.
  47. x, y = 1e16, 2.9999 # use temporary values to defeat peephole optimizer
  48. HAVE_DOUBLE_ROUNDING = (x + y == 1e16 + 4)
  49. if HAVE_DOUBLE_ROUNDING:
  50. skip("fsum is not exact on machines with double rounding")
  51. test_values = [
  52. ([], 0.0),
  53. ([0.0], 0.0),
  54. ([1e100, 1.0, -1e100, 1e-100, 1e50, -1.0, -1e50], 1e-100),
  55. ([2.0**53, -0.5, -2.0**-54], 2.0**53-1.0),
  56. ([2.0**53, 1.0, 2.0**-100], 2.0**53+2.0),
  57. ([2.0**53+10.0, 1.0, 2.0**-100], 2.0**53+12.0),
  58. ([2.0**53-4.0, 0.5, 2.0**-54], 2.0**53-3.0),
  59. ([1./n for n in range(1, 1001)],
  60. float.fromhex('0x1.df11f45f4e61ap+2')),
  61. ([(-1.)**n/n for n in range(1, 1001)],
  62. float.fromhex('-0x1.62a2af1bd3624p-1')),
  63. ([1.7**(i+1)-1.7**i for i in range(1000)] + [-1.7**1000], -1.0),
  64. ([1e16, 1., 1e-16], 10000000000000002.0),
  65. ([1e16-2., 1.-2.**-53, -(1e16-2.), -(1.-2.**-53)], 0.0),
  66. # exercise code for resizing partials array
  67. ([2.**n - 2.**(n+50) + 2.**(n+52) for n in range(-1074, 972, 2)] +
  68. [-2.**1022],
  69. float.fromhex('0x1.5555555555555p+970')),
  70. ]
  71. for i, (vals, expected) in enumerate(test_values):
  72. try:
  73. actual = math.fsum(vals)
  74. except OverflowError:
  75. py.test.fail("test %d failed: got OverflowError, expected %r "
  76. "for math.fsum(%.100r)" % (i, expected, vals))
  77. except ValueError:
  78. py.test.fail("test %d failed: got ValueError, expected %r "
  79. "for math.fsum(%.100r)" % (i, expected, vals))
  80. assert actual == expected
  81. def test_factorial(self):
  82. import math
  83. assert math.factorial(0) == 1
  84. assert math.factorial(1) == 1
  85. assert math.factorial(2) == 2
  86. assert math.factorial(5) == 120
  87. assert math.factorial(5.) == 120
  88. raises(ValueError, math.factorial, -1)
  89. raises(ValueError, math.factorial, -1.)
  90. raises(ValueError, math.factorial, 1.1)
  91. def test_log1p(self):
  92. import math
  93. self.ftest(math.log1p(1/math.e-1), -1)
  94. self.ftest(math.log1p(0), 0)
  95. self.ftest(math.log1p(math.e-1), 1)
  96. self.ftest(math.log1p(1), math.log(2))
  97. def test_acosh(self):
  98. import math
  99. self.ftest(math.acosh(1), 0)
  100. self.ftest(math.acosh(2), 1.3169578969248168)
  101. assert math.isinf(math.asinh(float("inf")))
  102. raises(ValueError, math.acosh, 0)
  103. def test_asinh(self):
  104. import math
  105. self.ftest(math.asinh(0), 0)
  106. self.ftest(math.asinh(1), 0.88137358701954305)
  107. self.ftest(math.asinh(-1), -0.88137358701954305)
  108. assert math.isinf(math.asinh(float("inf")))
  109. def test_atanh(self):
  110. import math
  111. self.ftest(math.atanh(0), 0)
  112. self.ftest(math.atanh(0.5), 0.54930614433405489)
  113. self.ftest(math.atanh(-0.5), -0.54930614433405489)
  114. raises(ValueError, math.atanh, 1.)
  115. assert math.isnan(math.atanh(float("nan")))
  116. def test_mtestfile(self):
  117. import math
  118. import abc
  119. import os
  120. import struct
  121. def _parse_mtestfile(fname):
  122. """Parse a file with test values
  123. -- starts a comment
  124. blank lines, or lines containing only a comment, are ignored
  125. other lines are expected to have the form
  126. id fn arg -> expected [flag]*
  127. """
  128. with open(fname) as fp:
  129. for line in fp:
  130. # strip comments, and skip blank lines
  131. if '--' in line:
  132. line = line[:line.index('--')]
  133. if not line.strip():
  134. continue
  135. lhs, rhs = line.split('->')
  136. id, fn, arg = lhs.split()
  137. rhs_pieces = rhs.split()
  138. exp = rhs_pieces[0]
  139. flags = rhs_pieces[1:]
  140. yield (id, fn, float(arg), float(exp), flags)
  141. def to_ulps(x):
  142. """Convert a non-NaN float x to an integer, in such a way that
  143. adjacent floats are converted to adjacent integers. Then
  144. abs(ulps(x) - ulps(y)) gives the difference in ulps between two
  145. floats.
  146. The results from this function will only make sense on platforms
  147. where C doubles are represented in IEEE 754 binary64 format.
  148. """
  149. n = struct.unpack('<q', struct.pack('<d', x))[0]
  150. if n < 0:
  151. n = ~(n+2**63)
  152. return n
  153. def ulps_check(expected, got, ulps=20):
  154. """Given non-NaN floats `expected` and `got`,
  155. check that they're equal to within the given number of ulps.
  156. Returns None on success and an error message on failure."""
  157. ulps_error = to_ulps(got) - to_ulps(expected)
  158. if abs(ulps_error) <= ulps:
  159. return None
  160. return "error = {} ulps; permitted error = {} ulps".format(ulps_error,
  161. ulps)
  162. def acc_check(expected, got, rel_err=2e-15, abs_err = 5e-323):
  163. """Determine whether non-NaN floats a and b are equal to within a
  164. (small) rounding error. The default values for rel_err and
  165. abs_err are chosen to be suitable for platforms where a float is
  166. represented by an IEEE 754 double. They allow an error of between
  167. 9 and 19 ulps."""
  168. # need to special case infinities, since inf - inf gives nan
  169. if math.isinf(expected) and got == expected:
  170. return None
  171. error = got - expected
  172. permitted_error = max(abs_err, rel_err * abs(expected))
  173. if abs(error) < permitted_error:
  174. return None
  175. return "error = {}; permitted error = {}".format(error,
  176. permitted_error)
  177. ALLOWED_ERROR = 20 # permitted error, in ulps
  178. fail_fmt = "{}:{}({!r}): expected {!r}, got {!r}"
  179. failures = []
  180. math_testcases = os.path.join(os.path.dirname(abc.__file__), "test",
  181. "math_testcases.txt")
  182. for id, fn, arg, expected, flags in _parse_mtestfile(math_testcases):
  183. func = getattr(math, fn)
  184. if 'invalid' in flags or 'divide-by-zero' in flags:
  185. expected = 'ValueError'
  186. elif 'overflow' in flags:
  187. expected = 'OverflowError'
  188. try:
  189. got = func(arg)
  190. except ValueError:
  191. got = 'ValueError'
  192. except OverflowError:
  193. got = 'OverflowError'
  194. accuracy_failure = None
  195. if isinstance(got, float) and isinstance(expected, float):
  196. if math.isnan(expected) and math.isnan(got):
  197. continue
  198. if not math.isnan(expected) and not math.isnan(got):
  199. if fn == 'lgamma':
  200. # we use a weaker accuracy test for lgamma;
  201. # lgamma only achieves an absolute error of
  202. # a few multiples of the machine accuracy, in
  203. # general.
  204. accuracy_failure = acc_check(expected, got,
  205. rel_err = 5e-15,
  206. abs_err = 5e-15)
  207. elif fn == 'erfc':
  208. # erfc has less-than-ideal accuracy for large
  209. # arguments (x ~ 25 or so), mainly due to the
  210. # error involved in computing exp(-x*x).
  211. #
  212. # XXX Would be better to weaken this test only
  213. # for large x, instead of for all x.
  214. accuracy_failure = ulps_check(expected, got, 2000)
  215. else:
  216. accuracy_failure = ulps_check(expected, got, 20)
  217. if accuracy_failure is None:
  218. continue
  219. if isinstance(got, str) and isinstance(expected, str):
  220. if got == expected:
  221. continue
  222. fail_msg = fail_fmt.format(id, fn, arg, expected, got)
  223. if accuracy_failure is not None:
  224. fail_msg += ' ({})'.format(accuracy_failure)
  225. failures.append(fail_msg)
  226. assert not failures
  227. def test_trunc(self):
  228. import math
  229. assert math.trunc(1.9) == 1.0
  230. raises((AttributeError, TypeError), math.trunc, 1.9j)
  231. class foo(object):
  232. def __trunc__(self):
  233. return "truncated"
  234. assert math.trunc(foo()) == "truncated"
  235. def test_copysign_nan(self):
  236. import math
  237. assert math.copysign(1.0, float('-nan')) == -1.0