PageRenderTime 44ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

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

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