Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(150674)

Side by Side Diff: Modules/_decimal/tests/deccheck.py

Issue 7652: Merge C version of decimal into py3k.
Patch Set: Created 7 years, 9 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « Modules/_decimal/tests/bench.py ('k') | Modules/_decimal/tests/formathelper.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
2
3 #
4 # Copyright (c) 2008-2010 Stefan Krah. All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
9 #
10 # 1. Redistributions of source code must retain the above copyright
11 # notice, this list of conditions and the following disclaimer.
12 #
13 # 2. Redistributions in binary form must reproduce the above copyright
14 # notice, this list of conditions and the following disclaimer in the
15 # documentation and/or other materials provided with the distribution.
16 #
17 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
18 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 # SUCH DAMAGE.
28 #
29
30 #
31 # Usage: python deccheck.py [--short|--medium|--long|--all]
32 #
33
34 import sys, random
35 from copy import copy
36 from collections import defaultdict
37 from test.support import import_fresh_module
38 from randdec import randfloat, all_unary, all_binary, all_ternary
39 from formathelper import rand_format, rand_locale
40
41 C = import_fresh_module('decimal', fresh=['_decimal'])
42 P = import_fresh_module('decimal', blocked=['_decimal'])
43 EXIT_STATUS = 0
44
45
46 # Contains all categories of Decimal methods.
47 Functions = {
48 # Plain unary:
49 'unary': (
50 '__abs__', '__bool__', '__ceil__', '__complex__', '__copy__',
51 '__floor__', '__float__', '__hash__', '__int__', '__neg__',
52 '__pos__', '__reduce__', '__repr__', '__str__', '__trunc__',
53 'adjusted', 'as_tuple', 'canonical', 'conjugate', 'copy_abs',
54 'copy_negate', 'is_canonical', 'is_finite', 'is_infinite',
55 'is_nan', 'is_qnan', 'is_signed', 'is_snan', 'is_zero', 'radix'
56 ),
57 # Unary with optional context:
58 'unary_ctx': (
59 'exp', 'is_normal', 'is_subnormal', 'ln', 'log10', 'logb',
60 'logical_invert', 'next_minus', 'next_plus', 'normalize',
61 'number_class', 'sqrt', 'to_eng_string'
62 ),
63 # Unary with optional rounding mode and context:
64 'unary_rnd_ctx': ('to_integral', 'to_integral_exact', 'to_integral_value'),
65 # Plain binary:
66 'binary': (
67 '__add__', '__divmod__', '__eq__', '__floordiv__', '__ge__', '__gt__',
68 '__le__', '__lt__', '__mod__', '__mul__', '__ne__', '__pow__',
69 '__radd__', '__rdivmod__', '__rfloordiv__', '__rmod__', '__rmul__',
70 '__rpow__', '__rsub__', '__rtruediv__', '__sub__', '__truediv__',
71 'compare_total', 'compare_total_mag', 'copy_sign', 'quantize',
72 'same_quantum'
73 ),
74 # Binary with optional context:
75 'binary_ctx': (
76 'compare', 'compare_signal', 'logical_and', 'logical_or', 'logical_xor',
77 'max', 'max_mag', 'min', 'min_mag', 'next_toward', 'remainder_near',
78 'rotate', 'scaleb', 'shift'
79 ),
80 # Plain ternary:
81 'ternary': ('__pow__',),
82 # Ternary with optional context:
83 'ternary_ctx': ('fma',),
84 # Special:
85 'special': ('__format__', '__reduce_ex__', '__round__', 'from_float',
86 'quantize'),
87 # Properties:
88 'property': ('real', 'imag')
89 }
90
91 # Contains all categories of Context methods. The n-ary classification
92 # applies to the number of Decimal arguments.
93 ContextFunctions = {
94 # Plain nullary:
95 'nullary': ('context.__hash__', 'context.__reduce__', 'context.radix'),
96 # Plain unary:
97 'unary': ('context.abs', 'context.canonical', 'context.copy_abs',
98 'context.copy_decimal', 'context.copy_negate',
99 'context.create_decimal', 'context.exp', 'context.is_canonical',
100 'context.is_finite', 'context.is_infinite', 'context.is_nan',
101 'context.is_normal', 'context.is_qnan', 'context.is_signed',
102 'context.is_snan', 'context.is_subnormal', 'context.is_zero',
103 'context.ln', 'context.log10', 'context.logb',
104 'context.logical_invert', 'context.minus', 'context.next_minus',
105 'context.next_plus', 'context.normalize', 'context.number_class',
106 'context.plus', 'context.sqrt', 'context.to_eng_string',
107 'context.to_integral', 'context.to_integral_exact',
108 'context.to_integral_value', 'context.to_sci_string'
109 ),
110 # Plain binary:
111 'binary': ('context.add', 'context.compare', 'context.compare_signal',
112 'context.compare_total', 'context.compare_total_mag',
113 'context.copy_sign', 'context.divide', 'context.divide_int',
114 'context.divmod', 'context.logical_and', 'context.logical_or',
115 'context.logical_xor', 'context.max', 'context.max_mag',
116 'context.min', 'context.min_mag', 'context.multiply',
117 'context.next_toward', 'context.power', 'context.quantize',
118 'context.remainder', 'context.remainder_near', 'context.rotate',
119 'context.same_quantum', 'context.scaleb', 'context.shift',
120 'context.subtract'
121 ),
122 # Plain ternary:
123 'ternary': ('context.fma', 'context.power'),
124 # Special:
125 'special': ('context.__reduce_ex__', 'context.create_decimal_from_float')
126 }
127
128 # Functions that require a restricted exponent range for reasonable runtimes.
129 UnaryRestricted = [
130 '__ceil__', '__floor__', '__int__', '__long__', '__trunc__',
131 'to_integral', 'to_integral_value'
132 ]
133
134 BinaryRestricted = ['__round__']
135
136 TernaryRestricted = ['__pow__', 'context.power']
137
138
139 # ======================================================================
140 # Unified Context
141 # ======================================================================
142
143 # Translate symbols.
144 CondMap = {
145 C.Clamped: P.Clamped,
146 C.ConversionSyntax: P.ConversionSyntax,
147 C.DivisionByZero: P.DivisionByZero,
148 C.DivisionImpossible: P.InvalidOperation,
149 C.DivisionUndefined: P.DivisionUndefined,
150 C.Inexact: P.Inexact,
151 C.InvalidContext: P.InvalidContext,
152 C.InvalidOperation: P.InvalidOperation,
153 C.Overflow: P.Overflow,
154 C.Rounded: P.Rounded,
155 C.Subnormal: P.Subnormal,
156 C.Underflow: P.Underflow,
157 }
158
159 RoundMap = {
160 C.ROUND_UP: P.ROUND_UP,
161 C.ROUND_DOWN: P.ROUND_DOWN,
162 C.ROUND_CEILING: P.ROUND_CEILING,
163 C.ROUND_FLOOR: P.ROUND_FLOOR,
164 C.ROUND_HALF_UP: P.ROUND_HALF_UP,
165 C.ROUND_HALF_DOWN: P.ROUND_HALF_DOWN,
166 C.ROUND_HALF_EVEN: P.ROUND_HALF_EVEN,
167 C.ROUND_05UP: P.ROUND_05UP
168 }
169 RoundModes = RoundMap.items()
170
171
172 class Context(object):
173 """Provides a convenient way of syncing the C and P contexts"""
174
175 __slots__ = ['c', 'p']
176
177 def __init__(self, c_ctx=None, p_ctx=None):
178 """Initialization is from the C context"""
179 self.c = C.getcontext() if c_ctx is None else c_ctx
180 self.p = P.getcontext() if p_ctx is None else p_ctx
181 self.p.prec = self.c.prec
182 self.p.Emin = self.c.Emin
183 self.p.Emax = self.c.Emax
184 self.p.rounding = RoundMap[self.c.rounding]
185 self.p.capitals = self.c.capitals
186 self.settraps([sig for sig in self.c.traps if self.c.traps[sig]])
187 self.setstatus([sig for sig in self.c.flags if self.c.flags[sig]])
188 self.p.clamp = self.c.clamp
189
190 def __str__(self):
191 return str(self.c) + '\n' + str(self.p)
192
193 def getprec(self):
194 assert(self.c.prec == self.p.prec)
195 return self.c.prec
196
197 def setprec(self, val):
198 self.c.prec = val
199 self.p.prec = val
200
201 def getemin(self):
202 assert(self.c.Emin == self.p.Emin)
203 return self.c.Emin
204
205 def setemin(self, val):
206 self.c.Emin = val
207 self.p.Emin = val
208
209 def getemax(self):
210 assert(self.c.Emax == self.p.Emax)
211 return self.c.Emax
212
213 def setemax(self, val):
214 self.c.Emax = val
215 self.p.Emax = val
216
217 def getround(self):
218 assert(self.c.rounding == RoundMap[self.p.rounding])
219 return self.c.rounding
220
221 def setround(self, val):
222 self.c.rounding = val
223 self.p.rounding = RoundMap[val]
224
225 def getcapitals(self):
226 assert(self.c.capitals == self.p.capitals)
227 return self.c.capitals
228
229 def setcapitals(self, val):
230 self.c.capitals = val
231 self.p.capitals = val
232
233 def getclamp(self):
234 assert(self.c.clamp == self.p.clamp)
235 return self.c.clamp
236
237 def setclamp(self, val):
238 self.c.clamp = val
239 self.p.clamp = val
240
241 prec = property(getprec, setprec)
242 Emin = property(getemin, setemin)
243 Emax = property(getemax, setemax)
244 rounding = property(getround, setround)
245 clamp = property(getclamp, setclamp)
246 capitals = property(getcapitals, setcapitals)
247
248 def clear_traps(self):
249 self.c.clear_traps()
250 for trap in self.p.traps:
251 self.p.traps[trap] = False
252
253 def clear_status(self):
254 self.c.clear_flags()
255 self.p.clear_flags()
256
257 def settraps(self, lst):
258 """lst: C signal list"""
259 self.clear_traps()
260 for signal in lst:
261 self.c.traps[signal] = True
262 self.p.traps[CondMap[signal]] = True
263
264 def setstatus(self, lst):
265 """lst: C signal list"""
266 self.clear_status()
267 for signal in lst:
268 self.c.flags[signal] = True
269 self.p.flags[CondMap[signal]] = True
270
271 def assert_eq_status(self):
272 """assert equality of C and P status"""
273 for signal in self.c.flags:
274 if signal == C.FloatOperation:
275 continue
276 if self.c.flags[signal] == (not self.p.flags[CondMap[signal]]):
277 return False
278 return True
279
280
281 # We don't want exceptions so that we can compare the status flags.
282 context = Context()
283 context.Emin = C.MIN_EMIN
284 context.Emax = C.MAX_EMAX
285 context.clear_traps()
286
287 # When creating decimals, _decimal is ultimately limited by the maximum
288 # context values. We emulate this restriction for decimal.py.
289 maxcontext = P.Context(
290 prec=C.MAX_PREC,
291 Emin=C.MIN_EMIN,
292 Emax=C.MAX_EMAX,
293 rounding=P.ROUND_HALF_UP,
294 capitals=1
295 )
296 maxcontext.clamp = 0
297
298 def RestrictedDecimal(value):
299 maxcontext.traps = copy(context.p.traps)
300 maxcontext.clear_flags()
301 if isinstance(value, str):
302 value = value.strip()
303 dec = maxcontext.create_decimal(value)
304 if maxcontext.flags[P.Inexact] or \
305 maxcontext.flags[P.Rounded] or \
306 maxcontext.flags[P.InvalidOperation]:
307 return context.p._raise_error(P.InvalidOperation)
308 return dec
309
310
311 # ======================================================================
312 # TestSet: Organize data and events during a single test case
313 # ======================================================================
314
315 class RestrictedList(list):
316 """List that can only be modified by appending items."""
317 def __getattribute__(self, name):
318 if name != 'append':
319 raise AttributeError("unsupported operation")
320 return list.__getattribute__(self, name)
321 def unsupported(self, *_):
322 raise AttributeError("unsupported operation")
323 __add__ = __delattr__ = __delitem__ = __iadd__ = __imul__ = unsupported
324 __mul__ = __reversed__ = __rmul__ = __setattr__ = __setitem__ = unsupported
325
326 class TestSet(object):
327 """A TestSet contains the original input operands, converted operands,
328 Python exceptions that occurred either during conversion or during
329 execution of the actual function, and the final results.
330
331 For safety, most attributes are lists that only support the append
332 operation.
333
334 If a function name is prefixed with 'context.', the corresponding
335 context method is called.
336 """
337 def __init__(self, funcname, operands):
338 if funcname.startswith("context."):
339 self.funcname = funcname.replace("context.", "")
340 self.contextfunc = True
341 else:
342 self.funcname = funcname
343 self.contextfunc = False
344 self.op = operands # raw operand tuple
345 self.context = context # context used for the operation
346 self.cop = RestrictedList() # converted C.Decimal operands
347 self.cex = RestrictedList() # Python exceptions for C.Decimal
348 self.cresults = RestrictedList() # C.Decimal results
349 self.pop = RestrictedList() # converted P.Decimal operands
350 self.pex = RestrictedList() # Python exceptions for P.Decimal
351 self.presults = RestrictedList() # P.Decimal results
352
353
354 # ======================================================================
355 # SkipHandler: skip known discrepancies
356 # ======================================================================
357
358 class SkipHandler:
359 """Handle known discrepancies between decimal.py and _decimal.so.
360 These are either ULP differences in the power function or
361 extremely minor issues."""
362
363 def __init__(self):
364 self.ulpdiff = 0
365 self.powmod_zeros = 0
366 self.maxctx = P.Context(Emax=10**18, Emin=-10**18)
367
368 def default(self, t):
369 return False
370 __ge__ = __gt__ = __le__ = __lt__ = __ne__ = __eq__ = default
371 __reduce__ = __format__ = __repr__ = __str__ = default
372
373 def harrison_ulp(self, dec):
374 """ftp://ftp.inria.fr/INRIA/publication/publi-pdf/RR/RR-5504.pdf"""
375 a = dec.next_plus()
376 b = dec.next_minus()
377 return abs(a - b)
378
379 def standard_ulp(self, dec, prec):
380 return P._dec_from_triple(0, '1', dec._exp+len(dec._int)-prec)
381
382 def rounding_direction(self, x, mode):
383 """Determine the effective direction of the rounding when
384 the exact result x is rounded according to mode.
385 Return -1 for downwards, 0 for undirected, 1 for upwards,
386 2 for ROUND_05UP."""
387 cmp = 1 if x.compare_total(P.Decimal("+0")) >= 0 else -1
388
389 if mode in (P.ROUND_HALF_EVEN, P.ROUND_HALF_UP, P.ROUND_HALF_DOWN):
390 return 0
391 elif mode == P.ROUND_CEILING:
392 return 1
393 elif mode == P.ROUND_FLOOR:
394 return -1
395 elif mode == P.ROUND_UP:
396 return cmp
397 elif mode == P.ROUND_DOWN:
398 return -cmp
399 elif mode == P.ROUND_05UP:
400 return 2
401 else:
402 raise ValueError("Unexpected rounding mode: %s" % mode)
403
404 def check_ulpdiff(self, exact, rounded):
405 # current precision
406 p = context.p.prec
407
408 # Convert infinities to the largest representable number + 1.
409 x = exact
410 if exact.is_infinite():
411 x = P._dec_from_triple(exact._sign, '10', context.p.Emax)
412 y = rounded
413 if rounded.is_infinite():
414 y = P._dec_from_triple(rounded._sign, '10', context.p.Emax)
415
416 # err = (rounded - exact) / ulp(rounded)
417 self.maxctx.prec = p * 2
418 t = self.maxctx.subtract(y, x)
419 if context.c._flags & C.DecClamped or \
420 context.c._flags & C.DecUnderflow:
421 # The standard ulp does not work in Underflow territory.
422 ulp = self.harrison_ulp(y)
423 else:
424 ulp = self.standard_ulp(y, p)
425 # Error in ulps.
426 err = self.maxctx.divide(t, ulp)
427
428 dir = self.rounding_direction(x, context.p.rounding)
429 if dir == 0:
430 if P.Decimal("-0.6") < err < P.Decimal("0.6"):
431 return True
432 elif dir == 1: # directed, upwards
433 if P.Decimal("-0.1") < err < P.Decimal("1.1"):
434 return True
435 elif dir == -1: # directed, downwards
436 if P.Decimal("-1.1") < err < P.Decimal("0.1"):
437 return True
438 else: # ROUND_05UP
439 if P.Decimal("-1.1") < err < P.Decimal("1.1"):
440 return True
441
442 print("ulp: %s error: %s exact: %s c_rounded: %s"
443 % (ulp, err, exact, rounded))
444 return False
445
446 def un_resolve_ulp(self, t):
447 """Check if results of _decimal's exp, ln and log10 functions are
448 within the allowed ulp ranges. This function is only called if
449 context.c._allcr is 0."""
450 # NaNs are beyond repair.
451 if t.rc.is_nan() or t.rp.is_nan():
452 return False
453
454 # "exact" result, double precision, half_even
455 self.maxctx.prec = context.p.prec * 2
456
457 # op might not be a Decimal for context methods.
458 op = P.Decimal(t.pop[0])
459 exact = getattr(op, t.funcname)(context=self.maxctx)
460
461 # _decimal's rounded result
462 rounded = P.Decimal(t.cresults[0])
463
464 self.ulpdiff += 1
465 return self.check_ulpdiff(exact, rounded)
466
467 def bin_resolve_ulp(self, t):
468 """Check if results of _decimal's power function are within the
469 allowed ulp ranges."""
470 # NaNs are beyond repair.
471 if t.rc.is_nan() or t.rp.is_nan():
472 return False
473
474 # "exact" result, double precision, half_even
475 self.maxctx.prec = context.p.prec * 2
476
477 op1, op2 = t.pop[0], t.pop[1]
478 if t.contextfunc:
479 exact = getattr(self.maxctx, t.funcname)(op1, op2)
480 else:
481 exact = getattr(op1, t.funcname)(op2, context=self.maxctx)
482
483 # _decimal's rounded result
484 rounded = P.Decimal(t.cresults[0])
485
486 self.ulpdiff += 1
487 return self.check_ulpdiff(exact, rounded)
488
489 ############################ Correct rounding #############################
490 def resolve_underflow(self, t):
491 """In extremely rare cases where the infinite precision result is just
492 below etiny, cdecimal does not set Subnormal/Underflow. Example:
493
494 setcontext(Context(prec=21, rounding=ROUND_UP, Emin=-55, Emax=85))
495 Decimal("1.00000000000000000000000000000000000000000000000"
496 "0000000100000000000000000000000000000000000000000"
497 "0000000000000025").ln()
498 """
499 if t.cresults != t.presults:
500 return False # Results must be identical.
501 if context.c.flags[C.Rounded] and \
502 context.c.flags[C.Inexact] and \
503 context.p.flags[P.Rounded] and \
504 context.p.flags[P.Inexact]:
505 return True # Subnormal/Underflow may be missing.
506 return False
507
508 def exp(self, t):
509 """Resolve Underflow or ULP difference."""
510 if context.c._allcr:
511 return self.resolve_underflow(t)
512 return self.un_resolve_ulp(t)
513
514 def log10(self, t):
515 """Resolve Underflow or ULP difference."""
516 if context.c._allcr:
517 return self.resolve_underflow(t)
518 return self.un_resolve_ulp(t)
519
520 def ln(self, t):
521 """Resolve Underflow or ULP difference."""
522 if context.c._allcr:
523 return self.resolve_underflow(t)
524 return self.un_resolve_ulp(t)
525
526 def __pow__(self, t):
527 """Always calls the resolve function. C.Decimal does not have correct
528 rounding for the power function."""
529 if context.c.flags[C.Rounded] and \
530 context.c.flags[C.Inexact] and \
531 context.p.flags[P.Rounded] and \
532 context.p.flags[P.Inexact]:
533 return self.bin_resolve_ulp(t)
534 else:
535 return False
536 power = __rpow__ = __pow__
537
538 ############################## Technicalities #############################
539 def __float__(self, t):
540 """NaN comparison in the verify() function obviously gives an
541 incorrect answer: nan == nan -> False"""
542 if t.cop[0].is_nan() and t.pop[0].is_nan():
543 return True
544 return False
545 __complex__ = __float__
546
547 def __radd__(self, t):
548 """decimal.py gives precedence to the first NaN; this is
549 not important, as __radd__ will not be called for
550 two decimal arguments."""
551 if t.rc.is_nan() and t.rp.is_nan():
552 return True
553 return False
554 __rmul__ = __radd__
555
556 ################################ Various ##################################
557 def __round__(self, t):
558 """Exception: Decimal('1').__round__(-100000000000000000000000000)
559 Should it really be InvalidOperation?"""
560 if t.rc is None and t.rp.is_nan():
561 return True
562 return False
563
564 shandler = SkipHandler()
565 def skip_error(t):
566 return getattr(shandler, t.funcname, shandler.default)(t)
567
568
569 # ======================================================================
570 # Handling verification errors
571 # ======================================================================
572
573 class VerifyError(Exception):
574 """Verification failed."""
575 pass
576
577 def function_as_string(t):
578 if t.contextfunc:
579 cargs = t.cop
580 pargs = t.pop
581 cfunc = "c_func: %s(" % t.funcname
582 pfunc = "p_func: %s(" % t.funcname
583 else:
584 cself, cargs = t.cop[0], t.cop[1:]
585 pself, pargs = t.pop[0], t.pop[1:]
586 cfunc = "c_func: %s.%s(" % (repr(cself), t.funcname)
587 pfunc = "p_func: %s.%s(" % (repr(pself), t.funcname)
588
589 err = cfunc
590 for arg in cargs:
591 err += "%s, " % repr(arg)
592 err = err.rstrip(", ")
593 err += ")\n"
594
595 err += pfunc
596 for arg in pargs:
597 err += "%s, " % repr(arg)
598 err = err.rstrip(", ")
599 err += ")"
600
601 return err
602
603 def raise_error(t):
604 global EXIT_STATUS
605
606 if skip_error(t):
607 return
608 EXIT_STATUS = 1
609
610 err = "Error in %s:\n\n" % t.funcname
611 err += "input operands: %s\n\n" % (t.op,)
612 err += function_as_string(t)
613 err += "\n\nc_result: %s\np_result: %s\n\n" % (t.cresults, t.presults)
614 err += "c_exceptions: %s\np_exceptions: %s\n\n" % (t.cex, t.pex)
615 err += "%s\n\n" % str(t.context)
616
617 raise VerifyError(err)
618
619
620 # ======================================================================
621 # Main testing functions
622 #
623 # The procedure is always (t is the TestSet):
624 #
625 # convert(t) -> Initialize the TestSet as necessary.
626 #
627 # Return 0 for early abortion (e.g. if a TypeError
628 # occurs during conversion, there is nothing to test).
629 #
630 # Return 1 for continuing with the test case.
631 #
632 # callfuncs(t) -> Call the relevant function for each implementation
633 # and record the results in the TestSet.
634 #
635 # verify(t) -> Verify the results. If verification fails, details
636 # are printed to stdout.
637 # ======================================================================
638
639 def convert(t, convstr=True):
640 """ t is the testset. At this stage the testset contains a tuple of
641 operands t.op of various types. For decimal methods the first
642 operand (self) is always converted to Decimal. If 'convstr' is
643 true, string operands are converted as well.
644
645 Context operands are of type deccheck.Context, rounding mode
646 operands are given as a tuple (C.rounding, P.rounding).
647
648 Other types (float, int, etc.) are left unchanged.
649 """
650 for i, op in enumerate(t.op):
651
652 context.clear_status()
653
654 if not t.contextfunc and i == 0 or \
655 convstr and isinstance(op, str):
656 try:
657 c = C.Decimal(op)
658 cex = None
659 except (TypeError, ValueError, OverflowError) as e:
660 c = None
661 cex = e.__class__
662
663 try:
664 p = RestrictedDecimal(op)
665 pex = None
666 except (TypeError, ValueError, OverflowError) as e:
667 p = None
668 pex = e.__class__
669
670 t.cop.append(c)
671 t.cex.append(cex)
672 t.pop.append(p)
673 t.pex.append(pex)
674
675 if cex is pex:
676 if str(c) != str(p) or not context.assert_eq_status():
677 raise_error(t)
678 if cex and pex:
679 # nothing to test
680 return 0
681 else:
682 raise_error(t)
683
684 elif isinstance(op, Context):
685 t.context = op
686 t.cop.append(op.c)
687 t.pop.append(op.p)
688
689 elif op in RoundModes:
690 t.cop.append(op[0])
691 t.pop.append(op[1])
692
693 else:
694 t.cop.append(op)
695 t.pop.append(op)
696
697 return 1
698
699 def callfuncs(t):
700 """ t is the testset. At this stage the testset contains operand lists
701 t.cop and t.pop for the C and Python versions of decimal.
702 For Decimal methods, the first operands are of type C.Decimal and
703 P.Decimal respectively. The remaining operands can have various types.
704 For Context methods, all operands can have any type.
705
706 t.rc and t.rp are the results of the operation.
707 """
708 context.clear_status()
709
710 try:
711 if t.contextfunc:
712 cargs = t.cop
713 t.rc = getattr(context.c, t.funcname)(*cargs)
714 else:
715 cself = t.cop[0]
716 cargs = t.cop[1:]
717 t.rc = getattr(cself, t.funcname)(*cargs)
718 t.cex.append(None)
719 except (TypeError, ValueError, OverflowError, MemoryError) as e:
720 t.rc = None
721 t.cex.append(e.__class__)
722
723 try:
724 if t.contextfunc:
725 pargs = t.pop
726 t.rp = getattr(context.p, t.funcname)(*pargs)
727 else:
728 pself = t.pop[0]
729 pargs = t.pop[1:]
730 t.rp = getattr(pself, t.funcname)(*pargs)
731 t.pex.append(None)
732 except (TypeError, ValueError, OverflowError, MemoryError) as e:
733 t.rp = None
734 t.pex.append(e.__class__)
735
736 def verify(t, stat):
737 """ t is the testset. At this stage the testset contains the following
738 tuples:
739
740 t.op: original operands
741 t.cop: C.Decimal operands (see convert for details)
742 t.pop: P.Decimal operands (see convert for details)
743 t.rc: C result
744 t.rp: Python result
745
746 t.rc and t.rp can have various types.
747 """
748 t.cresults.append(str(t.rc))
749 t.presults.append(str(t.rp))
750 if isinstance(t.rc, C.Decimal) and isinstance(t.rp, P.Decimal):
751 # General case: both results are Decimals.
752 t.cresults.append(t.rc.to_eng_string())
753 t.cresults.append(t.rc.as_tuple())
754 t.cresults.append(str(t.rc.imag))
755 t.cresults.append(str(t.rc.real))
756 t.presults.append(t.rp.to_eng_string())
757 t.presults.append(t.rp.as_tuple())
758 t.presults.append(str(t.rp.imag))
759 t.presults.append(str(t.rp.real))
760
761 nc = t.rc.number_class().lstrip('+-s')
762 stat[nc] += 1
763 else:
764 # Results from e.g. __divmod__ can only be compared as strings.
765 if not isinstance(t.rc, tuple) and not isinstance(t.rp, tuple):
766 if t.rc != t.rp:
767 raise_error(t)
768 stat[type(t.rc).__name__] += 1
769
770 # The return value lists must be equal.
771 if t.cresults != t.presults:
772 raise_error(t)
773 # The Python exception lists (TypeError, etc.) must be equal.
774 if t.cex != t.pex:
775 raise_error(t)
776 # The context flags must be equal.
777 if not t.context.assert_eq_status():
778 raise_error(t)
779
780
781 # ======================================================================
782 # Main test loops
783 #
784 # test_method(method, testspecs, testfunc) ->
785 #
786 # Loop through various context settings. The degree of
787 # thoroughness is determined by 'testspec'. For each
788 # setting, call 'testfunc'. Generally, 'testfunc' itself
789 # a loop, iterating through many test cases generated
790 # by the functions in randdec.py.
791 #
792 # test_n-ary(method, prec, exp_range, restricted_range, itr, stat) ->
793 #
794 # 'test_unary', 'test_binary' and 'test_ternary' are the
795 # main test functions passed to 'test_method'. They deal
796 # with the regular cases. The thoroughness of testing is
797 # determined by 'itr'.
798 #
799 # 'prec', 'exp_range' and 'restricted_range' are passed
800 # to the test-generating functions and limit the generated
801 # values. In some cases, for reasonable run times a
802 # maximum exponent of 9999 is required.
803 #
804 # The 'stat' parameter is passed down to the 'verify'
805 # function, which records statistics for the result values.
806 # ======================================================================
807
808 def log(fmt, args=None):
809 if args:
810 sys.stdout.write(''.join((fmt, '\n')) % args)
811 else:
812 sys.stdout.write(''.join((str(fmt), '\n')))
813 sys.stdout.flush()
814
815 def test_method(method, testspecs, testfunc):
816 """Iterate a test function through many context settings."""
817 log("testing %s ...", method)
818 stat = defaultdict(int)
819 for spec in testspecs:
820 if 'samples' in spec:
821 spec['prec'] = sorted(random.sample(range(1, 101),
822 spec['samples']))
823 for prec in spec['prec']:
824 context.prec = prec
825 for expts in spec['expts']:
826 emin, emax = expts
827 if emin == 'rand':
828 context.Emin = random.randrange(-1000, 0)
829 context.Emax = random.randrange(prec, 1000)
830 else:
831 context.Emin, context.Emax = emin, emax
832 if prec > context.Emax: continue
833 log(" prec: %d emin: %d emax: %d",
834 (context.prec, context.Emin, context.Emax))
835 restr_range = 9999 if context.Emax > 9999 else context.Emax+99
836 for rounding in sorted(RoundMap):
837 context.rounding = rounding
838 context.capitals = random.randrange(2)
839 if spec['clamp'] == 'rand':
840 context.clamp = random.randrange(2)
841 else:
842 context.clamp = spec['clamp']
843 exprange = context.c.Emax
844 testfunc(method, prec, exprange, restr_range,
845 spec['iter'], stat)
846 log(" result types: %s" % sorted([t for t in stat.items()]))
847
848 def test_unary(method, prec, exp_range, restricted_range, itr, stat):
849 """Iterate a unary function through many test cases."""
850 if method in UnaryRestricted:
851 exp_range = restricted_range
852 for op in all_unary(prec, exp_range, itr):
853 t = TestSet(method, op)
854 try:
855 if not convert(t):
856 continue
857 callfuncs(t)
858 verify(t, stat)
859 except VerifyError as err:
860 log(err)
861
862 def test_binary(method, prec, exp_range, restricted_range, itr, stat):
863 """Iterate a binary function through many test cases."""
864 if method in BinaryRestricted:
865 exp_range = restricted_range
866 for op in all_binary(prec, exp_range, itr):
867 t = TestSet(method, op)
868 try:
869 if not convert(t):
870 continue
871 callfuncs(t)
872 verify(t, stat)
873 except VerifyError as err:
874 log(err)
875
876 def test_ternary(method, prec, exp_range, restricted_range, itr, stat):
877 """Iterate a ternary function through many test cases."""
878 if method in TernaryRestricted:
879 exp_range = restricted_range
880 for op in all_ternary(prec, exp_range, itr):
881 t = TestSet(method, op)
882 try:
883 if not convert(t):
884 continue
885 callfuncs(t)
886 verify(t, stat)
887 except VerifyError as err:
888 log(err)
889
890 def test_format(method, prec, exp_range, restricted_range, itr, stat):
891 """Iterate the __format__ method through many test cases."""
892 for op in all_unary(prec, exp_range, itr):
893 fmt1 = rand_format(chr(random.randrange(32, 128)), 'EeGgn')
894 fmt2 = rand_locale()
895 for fmt in (fmt1, fmt2):
896 fmtop = (op[0], fmt)
897 t = TestSet(method, fmtop)
898 try:
899 if not convert(t, convstr=False):
900 continue
901 callfuncs(t)
902 verify(t, stat)
903 except VerifyError as err:
904 log(err)
905 for op in all_unary(prec, 9999, itr):
906 fmt1 = rand_format(chr(random.randrange(32, 128)), 'Ff%')
907 fmt2 = rand_locale()
908 for fmt in (fmt1, fmt2):
909 fmtop = (op[0], fmt)
910 t = TestSet(method, fmtop)
911 try:
912 if not convert(t, convstr=False):
913 continue
914 callfuncs(t)
915 verify(t, stat)
916 except VerifyError as err:
917 log(err)
918
919 def test_round(method, prec, exprange, restricted_range, itr, stat):
920 """Iterate the __round__ method through many test cases."""
921 for op in all_unary(prec, 9999, itr):
922 n = random.randrange(10)
923 roundop = (op[0], n)
924 t = TestSet(method, roundop)
925 try:
926 if not convert(t):
927 continue
928 callfuncs(t)
929 verify(t, stat)
930 except VerifyError as err:
931 log(err)
932
933 def test_from_float(method, prec, exprange, restricted_range, itr, stat):
934 """Iterate the __float__ method through many test cases."""
935 for rounding in sorted(RoundMap):
936 context.rounding = rounding
937 for i in range(1000):
938 f = randfloat()
939 op = (f,) if method.startswith("context.") else ("sNaN", f)
940 t = TestSet(method, op)
941 try:
942 if not convert(t):
943 continue
944 callfuncs(t)
945 verify(t, stat)
946 except VerifyError as err:
947 log(err)
948
949 def randcontext(exprange):
950 c = Context(C.Context(), P.Context())
951 c.Emax = random.randrange(1, exprange+1)
952 c.Emin = random.randrange(-exprange, 0)
953 maxprec = 100 if c.Emax >= 100 else c.Emax
954 c.prec = random.randrange(1, maxprec+1)
955 c.clamp = random.randrange(2)
956 c.clear_traps()
957 return c
958
959 def test_quantize_api(method, prec, exprange, restricted_range, itr, stat):
960 """Iterate the 'quantize' method through many test cases, using
961 the optional arguments."""
962 for op in all_binary(prec, restricted_range, itr):
963 for rounding in RoundModes:
964 c = randcontext(exprange)
965 quantizeop = (op[0], op[1], rounding, c)
966 t = TestSet(method, quantizeop)
967 try:
968 if not convert(t):
969 continue
970 callfuncs(t)
971 verify(t, stat)
972 except VerifyError as err:
973 log(err)
974
975
976 def check_untested(funcdict, c_cls, p_cls):
977 """Determine untested, C-only and Python-only attributes.
978 Uncomment print lines for debugging."""
979 c_attr = set(dir(c_cls))
980 p_attr = set(dir(p_cls))
981 intersect = c_attr & p_attr
982
983 funcdict['c_only'] = tuple(sorted(c_attr-intersect))
984 funcdict['p_only'] = tuple(sorted(p_attr-intersect))
985
986 tested = set()
987 for lst in funcdict.values():
988 for v in lst:
989 v = v.replace("context.", "") if c_cls == C.Context else v
990 tested.add(v)
991
992 funcdict['untested'] = tuple(sorted(intersect-tested))
993
994 #for key in ('untested', 'c_only', 'p_only'):
995 # s = 'Context' if c_cls == C.Context else 'Decimal'
996 # print("\n%s %s:\n%s" % (s, key, funcdict[key]))
997
998
999 if __name__ == '__main__':
1000
1001 import time
1002
1003 randseed = int(time.time())
1004 random.seed(randseed)
1005
1006 # Set up the testspecs list. A testspec is simply a dictionary
1007 # that determines the amount of different contexts that 'test_method'
1008 # will generate.
1009 base_expts = [(C.MIN_EMIN, C.MAX_EMAX)]
1010 if C.MAX_EMAX == 999999999999999999:
1011 base_expts.append((-999999999, 999999999))
1012
1013 # Basic contexts.
1014 base = {
1015 'expts': base_expts,
1016 'prec': [],
1017 'clamp': 'rand',
1018 'iter': None,
1019 'samples': None,
1020 }
1021 # Contexts with small values for prec, emin, emax.
1022 small = {
1023 'prec': [1, 2, 3, 4, 5],
1024 'expts': [(-1, 1), (-2, 2), (-3, 3), (-4, 4), (-5, 5)],
1025 'clamp': 'rand',
1026 'iter': None
1027 }
1028 # IEEE interchange format.
1029 ieee = [
1030 # DECIMAL32
1031 {'prec': [7], 'expts': [(-95, 96)], 'clamp': 1, 'iter': None},
1032 # DECIMAL64
1033 {'prec': [16], 'expts': [(-383, 384)], 'clamp': 1, 'iter': None},
1034 # DECIMAL128
1035 {'prec': [34], 'expts': [(-6143, 6144)], 'clamp': 1, 'iter': None}
1036 ]
1037
1038 if '--medium' in sys.argv:
1039 base['expts'].append(('rand', 'rand'))
1040 # 5 random precisions
1041 base['samples'] = 5
1042 testspecs = [small] + ieee + [base]
1043 if '--long' in sys.argv:
1044 base['expts'].append(('rand', 'rand'))
1045 # 10 random precisions
1046 base['samples'] = 10
1047 testspecs = [small] + ieee + [base]
1048 elif '--all' in sys.argv:
1049 base['expts'].append(('rand', 'rand'))
1050 # All precisions in [1, 100]
1051 base['samples'] = 100
1052 testspecs = [small] + ieee + [base]
1053 else: # --short
1054 rand_ieee = random.choice(ieee)
1055 base['iter'] = small['iter'] = rand_ieee['iter'] = 1
1056 # 1 random precision and exponent pair
1057 base['samples'] = 1
1058 base['expts'] = [random.choice(base_expts)]
1059 # 1 random precision and exponent pair
1060 prec = random.randrange(1, 6)
1061 small['prec'] = [prec]
1062 small['expts'] = [(-prec, prec)]
1063 testspecs = [small, rand_ieee, base]
1064
1065 check_untested(Functions, C.Decimal, P.Decimal)
1066 check_untested(ContextFunctions, C.Context, P.Context)
1067
1068
1069 log("\n\nRandom seed: %d\n\n", randseed)
1070
1071 # Decimal methods:
1072 for method in Functions['unary'] + Functions['unary_ctx'] + \
1073 Functions['unary_rnd_ctx']:
1074 test_method(method, testspecs, test_unary)
1075
1076 for method in Functions['binary'] + Functions['binary_ctx']:
1077 test_method(method, testspecs, test_binary)
1078
1079 for method in Functions['ternary'] + Functions['ternary_ctx']:
1080 test_method(method, testspecs, test_ternary)
1081
1082 test_method('__format__', testspecs, test_format)
1083 test_method('__round__', testspecs, test_round)
1084 test_method('from_float', testspecs, test_from_float)
1085 test_method('quantize', testspecs, test_quantize_api)
1086
1087 # Context methods:
1088 for method in ContextFunctions['unary']:
1089 test_method(method, testspecs, test_unary)
1090
1091 for method in ContextFunctions['binary']:
1092 test_method(method, testspecs, test_binary)
1093
1094 for method in ContextFunctions['ternary']:
1095 test_method(method, testspecs, test_ternary)
1096
1097 test_method('context.create_decimal_from_float', testspecs, test_from_float)
1098
1099
1100 sys.exit(EXIT_STATUS)
OLDNEW
« no previous file with comments | « Modules/_decimal/tests/bench.py ('k') | Modules/_decimal/tests/formathelper.py » ('j') | no next file with comments »

RSS Feeds Recent Issues | This issue
This is Rietveld 894c83f36cb7+