1# 2# Copyright (c) 2008-2012 Stefan Krah. All rights reserved. 3# 4# Redistribution and use in source and binary forms, with or without 5# modification, are permitted provided that the following conditions 6# are met: 7# 8# 1. Redistributions of source code must retain the above copyright 9# notice, this list of conditions and the following disclaimer. 10# 11# 2. Redistributions in binary form must reproduce the above copyright 12# notice, this list of conditions and the following disclaimer in the 13# documentation and/or other materials provided with the distribution. 14# 15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND 16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25# SUCH DAMAGE. 26# 27 28# 29# Usage: python deccheck.py [--short|--medium|--long|--all] 30# 31 32import sys, random 33from copy import copy 34from collections import defaultdict 35from test.support import import_fresh_module 36from randdec import randfloat, all_unary, all_binary, all_ternary 37from randdec import unary_optarg, binary_optarg, ternary_optarg 38from formathelper import rand_format, rand_locale 39from _pydecimal import _dec_from_triple 40 41C = import_fresh_module('decimal', fresh=['_decimal']) 42P = import_fresh_module('decimal', blocked=['_decimal']) 43EXIT_STATUS = 0 44 45 46# Contains all categories of Decimal methods. 47Functions = { 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_integer_ratio', 'as_tuple', 'canonical', 'conjugate', 54 'copy_abs', '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. 93ContextFunctions = { 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. 129UnaryRestricted = [ 130 '__ceil__', '__floor__', '__int__', '__trunc__', 131 'as_integer_ratio', 'to_integral', 'to_integral_value' 132] 133 134BinaryRestricted = ['__round__'] 135 136TernaryRestricted = ['__pow__', 'context.power'] 137 138 139# ====================================================================== 140# Unified Context 141# ====================================================================== 142 143# Translate symbols. 144CondMap = { 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 C.FloatOperation: P.FloatOperation, 158} 159 160RoundModes = [C.ROUND_UP, C.ROUND_DOWN, C.ROUND_CEILING, C.ROUND_FLOOR, 161 C.ROUND_HALF_UP, C.ROUND_HALF_DOWN, C.ROUND_HALF_EVEN, 162 C.ROUND_05UP] 163 164 165class Context(object): 166 """Provides a convenient way of syncing the C and P contexts""" 167 168 __slots__ = ['c', 'p'] 169 170 def __init__(self, c_ctx=None, p_ctx=None): 171 """Initialization is from the C context""" 172 self.c = C.getcontext() if c_ctx is None else c_ctx 173 self.p = P.getcontext() if p_ctx is None else p_ctx 174 self.p.prec = self.c.prec 175 self.p.Emin = self.c.Emin 176 self.p.Emax = self.c.Emax 177 self.p.rounding = self.c.rounding 178 self.p.capitals = self.c.capitals 179 self.settraps([sig for sig in self.c.traps if self.c.traps[sig]]) 180 self.setstatus([sig for sig in self.c.flags if self.c.flags[sig]]) 181 self.p.clamp = self.c.clamp 182 183 def __str__(self): 184 return str(self.c) + '\n' + str(self.p) 185 186 def getprec(self): 187 assert(self.c.prec == self.p.prec) 188 return self.c.prec 189 190 def setprec(self, val): 191 self.c.prec = val 192 self.p.prec = val 193 194 def getemin(self): 195 assert(self.c.Emin == self.p.Emin) 196 return self.c.Emin 197 198 def setemin(self, val): 199 self.c.Emin = val 200 self.p.Emin = val 201 202 def getemax(self): 203 assert(self.c.Emax == self.p.Emax) 204 return self.c.Emax 205 206 def setemax(self, val): 207 self.c.Emax = val 208 self.p.Emax = val 209 210 def getround(self): 211 assert(self.c.rounding == self.p.rounding) 212 return self.c.rounding 213 214 def setround(self, val): 215 self.c.rounding = val 216 self.p.rounding = val 217 218 def getcapitals(self): 219 assert(self.c.capitals == self.p.capitals) 220 return self.c.capitals 221 222 def setcapitals(self, val): 223 self.c.capitals = val 224 self.p.capitals = val 225 226 def getclamp(self): 227 assert(self.c.clamp == self.p.clamp) 228 return self.c.clamp 229 230 def setclamp(self, val): 231 self.c.clamp = val 232 self.p.clamp = val 233 234 prec = property(getprec, setprec) 235 Emin = property(getemin, setemin) 236 Emax = property(getemax, setemax) 237 rounding = property(getround, setround) 238 clamp = property(getclamp, setclamp) 239 capitals = property(getcapitals, setcapitals) 240 241 def clear_traps(self): 242 self.c.clear_traps() 243 for trap in self.p.traps: 244 self.p.traps[trap] = False 245 246 def clear_status(self): 247 self.c.clear_flags() 248 self.p.clear_flags() 249 250 def settraps(self, lst): 251 """lst: C signal list""" 252 self.clear_traps() 253 for signal in lst: 254 self.c.traps[signal] = True 255 self.p.traps[CondMap[signal]] = True 256 257 def setstatus(self, lst): 258 """lst: C signal list""" 259 self.clear_status() 260 for signal in lst: 261 self.c.flags[signal] = True 262 self.p.flags[CondMap[signal]] = True 263 264 def assert_eq_status(self): 265 """assert equality of C and P status""" 266 for signal in self.c.flags: 267 if self.c.flags[signal] == (not self.p.flags[CondMap[signal]]): 268 return False 269 return True 270 271 272# We don't want exceptions so that we can compare the status flags. 273context = Context() 274context.Emin = C.MIN_EMIN 275context.Emax = C.MAX_EMAX 276context.clear_traps() 277 278# When creating decimals, _decimal is ultimately limited by the maximum 279# context values. We emulate this restriction for decimal.py. 280maxcontext = P.Context( 281 prec=C.MAX_PREC, 282 Emin=C.MIN_EMIN, 283 Emax=C.MAX_EMAX, 284 rounding=P.ROUND_HALF_UP, 285 capitals=1 286) 287maxcontext.clamp = 0 288 289def RestrictedDecimal(value): 290 maxcontext.traps = copy(context.p.traps) 291 maxcontext.clear_flags() 292 if isinstance(value, str): 293 value = value.strip() 294 dec = maxcontext.create_decimal(value) 295 if maxcontext.flags[P.Inexact] or \ 296 maxcontext.flags[P.Rounded] or \ 297 maxcontext.flags[P.Clamped] or \ 298 maxcontext.flags[P.InvalidOperation]: 299 return context.p._raise_error(P.InvalidOperation) 300 if maxcontext.flags[P.FloatOperation]: 301 context.p.flags[P.FloatOperation] = True 302 return dec 303 304 305# ====================================================================== 306# TestSet: Organize data and events during a single test case 307# ====================================================================== 308 309class RestrictedList(list): 310 """List that can only be modified by appending items.""" 311 def __getattribute__(self, name): 312 if name != 'append': 313 raise AttributeError("unsupported operation") 314 return list.__getattribute__(self, name) 315 def unsupported(self, *_): 316 raise AttributeError("unsupported operation") 317 __add__ = __delattr__ = __delitem__ = __iadd__ = __imul__ = unsupported 318 __mul__ = __reversed__ = __rmul__ = __setattr__ = __setitem__ = unsupported 319 320class TestSet(object): 321 """A TestSet contains the original input operands, converted operands, 322 Python exceptions that occurred either during conversion or during 323 execution of the actual function, and the final results. 324 325 For safety, most attributes are lists that only support the append 326 operation. 327 328 If a function name is prefixed with 'context.', the corresponding 329 context method is called. 330 """ 331 def __init__(self, funcname, operands): 332 if funcname.startswith("context."): 333 self.funcname = funcname.replace("context.", "") 334 self.contextfunc = True 335 else: 336 self.funcname = funcname 337 self.contextfunc = False 338 self.op = operands # raw operand tuple 339 self.context = context # context used for the operation 340 self.cop = RestrictedList() # converted C.Decimal operands 341 self.cex = RestrictedList() # Python exceptions for C.Decimal 342 self.cresults = RestrictedList() # C.Decimal results 343 self.pop = RestrictedList() # converted P.Decimal operands 344 self.pex = RestrictedList() # Python exceptions for P.Decimal 345 self.presults = RestrictedList() # P.Decimal results 346 347 348# ====================================================================== 349# SkipHandler: skip known discrepancies 350# ====================================================================== 351 352class SkipHandler: 353 """Handle known discrepancies between decimal.py and _decimal.so. 354 These are either ULP differences in the power function or 355 extremely minor issues.""" 356 357 def __init__(self): 358 self.ulpdiff = 0 359 self.powmod_zeros = 0 360 self.maxctx = P.Context(Emax=10**18, Emin=-10**18) 361 362 def default(self, t): 363 return False 364 __ge__ = __gt__ = __le__ = __lt__ = __ne__ = __eq__ = default 365 __reduce__ = __format__ = __repr__ = __str__ = default 366 367 def harrison_ulp(self, dec): 368 """ftp://ftp.inria.fr/INRIA/publication/publi-pdf/RR/RR-5504.pdf""" 369 a = dec.next_plus() 370 b = dec.next_minus() 371 return abs(a - b) 372 373 def standard_ulp(self, dec, prec): 374 return _dec_from_triple(0, '1', dec._exp+len(dec._int)-prec) 375 376 def rounding_direction(self, x, mode): 377 """Determine the effective direction of the rounding when 378 the exact result x is rounded according to mode. 379 Return -1 for downwards, 0 for undirected, 1 for upwards, 380 2 for ROUND_05UP.""" 381 cmp = 1 if x.compare_total(P.Decimal("+0")) >= 0 else -1 382 383 if mode in (P.ROUND_HALF_EVEN, P.ROUND_HALF_UP, P.ROUND_HALF_DOWN): 384 return 0 385 elif mode == P.ROUND_CEILING: 386 return 1 387 elif mode == P.ROUND_FLOOR: 388 return -1 389 elif mode == P.ROUND_UP: 390 return cmp 391 elif mode == P.ROUND_DOWN: 392 return -cmp 393 elif mode == P.ROUND_05UP: 394 return 2 395 else: 396 raise ValueError("Unexpected rounding mode: %s" % mode) 397 398 def check_ulpdiff(self, exact, rounded): 399 # current precision 400 p = context.p.prec 401 402 # Convert infinities to the largest representable number + 1. 403 x = exact 404 if exact.is_infinite(): 405 x = _dec_from_triple(exact._sign, '10', context.p.Emax) 406 y = rounded 407 if rounded.is_infinite(): 408 y = _dec_from_triple(rounded._sign, '10', context.p.Emax) 409 410 # err = (rounded - exact) / ulp(rounded) 411 self.maxctx.prec = p * 2 412 t = self.maxctx.subtract(y, x) 413 if context.c.flags[C.Clamped] or \ 414 context.c.flags[C.Underflow]: 415 # The standard ulp does not work in Underflow territory. 416 ulp = self.harrison_ulp(y) 417 else: 418 ulp = self.standard_ulp(y, p) 419 # Error in ulps. 420 err = self.maxctx.divide(t, ulp) 421 422 dir = self.rounding_direction(x, context.p.rounding) 423 if dir == 0: 424 if P.Decimal("-0.6") < err < P.Decimal("0.6"): 425 return True 426 elif dir == 1: # directed, upwards 427 if P.Decimal("-0.1") < err < P.Decimal("1.1"): 428 return True 429 elif dir == -1: # directed, downwards 430 if P.Decimal("-1.1") < err < P.Decimal("0.1"): 431 return True 432 else: # ROUND_05UP 433 if P.Decimal("-1.1") < err < P.Decimal("1.1"): 434 return True 435 436 print("ulp: %s error: %s exact: %s c_rounded: %s" 437 % (ulp, err, exact, rounded)) 438 return False 439 440 def bin_resolve_ulp(self, t): 441 """Check if results of _decimal's power function are within the 442 allowed ulp ranges.""" 443 # NaNs are beyond repair. 444 if t.rc.is_nan() or t.rp.is_nan(): 445 return False 446 447 # "exact" result, double precision, half_even 448 self.maxctx.prec = context.p.prec * 2 449 450 op1, op2 = t.pop[0], t.pop[1] 451 if t.contextfunc: 452 exact = getattr(self.maxctx, t.funcname)(op1, op2) 453 else: 454 exact = getattr(op1, t.funcname)(op2, context=self.maxctx) 455 456 # _decimal's rounded result 457 rounded = P.Decimal(t.cresults[0]) 458 459 self.ulpdiff += 1 460 return self.check_ulpdiff(exact, rounded) 461 462 ############################ Correct rounding ############################# 463 def resolve_underflow(self, t): 464 """In extremely rare cases where the infinite precision result is just 465 below etiny, cdecimal does not set Subnormal/Underflow. Example: 466 467 setcontext(Context(prec=21, rounding=ROUND_UP, Emin=-55, Emax=85)) 468 Decimal("1.00000000000000000000000000000000000000000000000" 469 "0000000100000000000000000000000000000000000000000" 470 "0000000000000025").ln() 471 """ 472 if t.cresults != t.presults: 473 return False # Results must be identical. 474 if context.c.flags[C.Rounded] and \ 475 context.c.flags[C.Inexact] and \ 476 context.p.flags[P.Rounded] and \ 477 context.p.flags[P.Inexact]: 478 return True # Subnormal/Underflow may be missing. 479 return False 480 481 def exp(self, t): 482 """Resolve Underflow or ULP difference.""" 483 return self.resolve_underflow(t) 484 485 def log10(self, t): 486 """Resolve Underflow or ULP difference.""" 487 return self.resolve_underflow(t) 488 489 def ln(self, t): 490 """Resolve Underflow or ULP difference.""" 491 return self.resolve_underflow(t) 492 493 def __pow__(self, t): 494 """Always calls the resolve function. C.Decimal does not have correct 495 rounding for the power function.""" 496 if context.c.flags[C.Rounded] and \ 497 context.c.flags[C.Inexact] and \ 498 context.p.flags[P.Rounded] and \ 499 context.p.flags[P.Inexact]: 500 return self.bin_resolve_ulp(t) 501 else: 502 return False 503 power = __rpow__ = __pow__ 504 505 ############################## Technicalities ############################# 506 def __float__(self, t): 507 """NaN comparison in the verify() function obviously gives an 508 incorrect answer: nan == nan -> False""" 509 if t.cop[0].is_nan() and t.pop[0].is_nan(): 510 return True 511 return False 512 __complex__ = __float__ 513 514 def __radd__(self, t): 515 """decimal.py gives precedence to the first NaN; this is 516 not important, as __radd__ will not be called for 517 two decimal arguments.""" 518 if t.rc.is_nan() and t.rp.is_nan(): 519 return True 520 return False 521 __rmul__ = __radd__ 522 523 ################################ Various ################################## 524 def __round__(self, t): 525 """Exception: Decimal('1').__round__(-100000000000000000000000000) 526 Should it really be InvalidOperation?""" 527 if t.rc is None and t.rp.is_nan(): 528 return True 529 return False 530 531shandler = SkipHandler() 532def skip_error(t): 533 return getattr(shandler, t.funcname, shandler.default)(t) 534 535 536# ====================================================================== 537# Handling verification errors 538# ====================================================================== 539 540class VerifyError(Exception): 541 """Verification failed.""" 542 pass 543 544def function_as_string(t): 545 if t.contextfunc: 546 cargs = t.cop 547 pargs = t.pop 548 cfunc = "c_func: %s(" % t.funcname 549 pfunc = "p_func: %s(" % t.funcname 550 else: 551 cself, cargs = t.cop[0], t.cop[1:] 552 pself, pargs = t.pop[0], t.pop[1:] 553 cfunc = "c_func: %s.%s(" % (repr(cself), t.funcname) 554 pfunc = "p_func: %s.%s(" % (repr(pself), t.funcname) 555 556 err = cfunc 557 for arg in cargs: 558 err += "%s, " % repr(arg) 559 err = err.rstrip(", ") 560 err += ")\n" 561 562 err += pfunc 563 for arg in pargs: 564 err += "%s, " % repr(arg) 565 err = err.rstrip(", ") 566 err += ")" 567 568 return err 569 570def raise_error(t): 571 global EXIT_STATUS 572 573 if skip_error(t): 574 return 575 EXIT_STATUS = 1 576 577 err = "Error in %s:\n\n" % t.funcname 578 err += "input operands: %s\n\n" % (t.op,) 579 err += function_as_string(t) 580 err += "\n\nc_result: %s\np_result: %s\n\n" % (t.cresults, t.presults) 581 err += "c_exceptions: %s\np_exceptions: %s\n\n" % (t.cex, t.pex) 582 err += "%s\n\n" % str(t.context) 583 584 raise VerifyError(err) 585 586 587# ====================================================================== 588# Main testing functions 589# 590# The procedure is always (t is the TestSet): 591# 592# convert(t) -> Initialize the TestSet as necessary. 593# 594# Return 0 for early abortion (e.g. if a TypeError 595# occurs during conversion, there is nothing to test). 596# 597# Return 1 for continuing with the test case. 598# 599# callfuncs(t) -> Call the relevant function for each implementation 600# and record the results in the TestSet. 601# 602# verify(t) -> Verify the results. If verification fails, details 603# are printed to stdout. 604# ====================================================================== 605 606def convert(t, convstr=True): 607 """ t is the testset. At this stage the testset contains a tuple of 608 operands t.op of various types. For decimal methods the first 609 operand (self) is always converted to Decimal. If 'convstr' is 610 true, string operands are converted as well. 611 612 Context operands are of type deccheck.Context, rounding mode 613 operands are given as a tuple (C.rounding, P.rounding). 614 615 Other types (float, int, etc.) are left unchanged. 616 """ 617 for i, op in enumerate(t.op): 618 619 context.clear_status() 620 621 if op in RoundModes: 622 t.cop.append(op) 623 t.pop.append(op) 624 625 elif not t.contextfunc and i == 0 or \ 626 convstr and isinstance(op, str): 627 try: 628 c = C.Decimal(op) 629 cex = None 630 except (TypeError, ValueError, OverflowError) as e: 631 c = None 632 cex = e.__class__ 633 634 try: 635 p = RestrictedDecimal(op) 636 pex = None 637 except (TypeError, ValueError, OverflowError) as e: 638 p = None 639 pex = e.__class__ 640 641 t.cop.append(c) 642 t.cex.append(cex) 643 t.pop.append(p) 644 t.pex.append(pex) 645 646 if cex is pex: 647 if str(c) != str(p) or not context.assert_eq_status(): 648 raise_error(t) 649 if cex and pex: 650 # nothing to test 651 return 0 652 else: 653 raise_error(t) 654 655 elif isinstance(op, Context): 656 t.context = op 657 t.cop.append(op.c) 658 t.pop.append(op.p) 659 660 else: 661 t.cop.append(op) 662 t.pop.append(op) 663 664 return 1 665 666def callfuncs(t): 667 """ t is the testset. At this stage the testset contains operand lists 668 t.cop and t.pop for the C and Python versions of decimal. 669 For Decimal methods, the first operands are of type C.Decimal and 670 P.Decimal respectively. The remaining operands can have various types. 671 For Context methods, all operands can have any type. 672 673 t.rc and t.rp are the results of the operation. 674 """ 675 context.clear_status() 676 677 try: 678 if t.contextfunc: 679 cargs = t.cop 680 t.rc = getattr(context.c, t.funcname)(*cargs) 681 else: 682 cself = t.cop[0] 683 cargs = t.cop[1:] 684 t.rc = getattr(cself, t.funcname)(*cargs) 685 t.cex.append(None) 686 except (TypeError, ValueError, OverflowError, MemoryError) as e: 687 t.rc = None 688 t.cex.append(e.__class__) 689 690 try: 691 if t.contextfunc: 692 pargs = t.pop 693 t.rp = getattr(context.p, t.funcname)(*pargs) 694 else: 695 pself = t.pop[0] 696 pargs = t.pop[1:] 697 t.rp = getattr(pself, t.funcname)(*pargs) 698 t.pex.append(None) 699 except (TypeError, ValueError, OverflowError, MemoryError) as e: 700 t.rp = None 701 t.pex.append(e.__class__) 702 703def verify(t, stat): 704 """ t is the testset. At this stage the testset contains the following 705 tuples: 706 707 t.op: original operands 708 t.cop: C.Decimal operands (see convert for details) 709 t.pop: P.Decimal operands (see convert for details) 710 t.rc: C result 711 t.rp: Python result 712 713 t.rc and t.rp can have various types. 714 """ 715 t.cresults.append(str(t.rc)) 716 t.presults.append(str(t.rp)) 717 if isinstance(t.rc, C.Decimal) and isinstance(t.rp, P.Decimal): 718 # General case: both results are Decimals. 719 t.cresults.append(t.rc.to_eng_string()) 720 t.cresults.append(t.rc.as_tuple()) 721 t.cresults.append(str(t.rc.imag)) 722 t.cresults.append(str(t.rc.real)) 723 t.presults.append(t.rp.to_eng_string()) 724 t.presults.append(t.rp.as_tuple()) 725 t.presults.append(str(t.rp.imag)) 726 t.presults.append(str(t.rp.real)) 727 728 nc = t.rc.number_class().lstrip('+-s') 729 stat[nc] += 1 730 else: 731 # Results from e.g. __divmod__ can only be compared as strings. 732 if not isinstance(t.rc, tuple) and not isinstance(t.rp, tuple): 733 if t.rc != t.rp: 734 raise_error(t) 735 stat[type(t.rc).__name__] += 1 736 737 # The return value lists must be equal. 738 if t.cresults != t.presults: 739 raise_error(t) 740 # The Python exception lists (TypeError, etc.) must be equal. 741 if t.cex != t.pex: 742 raise_error(t) 743 # The context flags must be equal. 744 if not t.context.assert_eq_status(): 745 raise_error(t) 746 747 748# ====================================================================== 749# Main test loops 750# 751# test_method(method, testspecs, testfunc) -> 752# 753# Loop through various context settings. The degree of 754# thoroughness is determined by 'testspec'. For each 755# setting, call 'testfunc'. Generally, 'testfunc' itself 756# a loop, iterating through many test cases generated 757# by the functions in randdec.py. 758# 759# test_n-ary(method, prec, exp_range, restricted_range, itr, stat) -> 760# 761# 'test_unary', 'test_binary' and 'test_ternary' are the 762# main test functions passed to 'test_method'. They deal 763# with the regular cases. The thoroughness of testing is 764# determined by 'itr'. 765# 766# 'prec', 'exp_range' and 'restricted_range' are passed 767# to the test-generating functions and limit the generated 768# values. In some cases, for reasonable run times a 769# maximum exponent of 9999 is required. 770# 771# The 'stat' parameter is passed down to the 'verify' 772# function, which records statistics for the result values. 773# ====================================================================== 774 775def log(fmt, args=None): 776 if args: 777 sys.stdout.write(''.join((fmt, '\n')) % args) 778 else: 779 sys.stdout.write(''.join((str(fmt), '\n'))) 780 sys.stdout.flush() 781 782def test_method(method, testspecs, testfunc): 783 """Iterate a test function through many context settings.""" 784 log("testing %s ...", method) 785 stat = defaultdict(int) 786 for spec in testspecs: 787 if 'samples' in spec: 788 spec['prec'] = sorted(random.sample(range(1, 101), 789 spec['samples'])) 790 for prec in spec['prec']: 791 context.prec = prec 792 for expts in spec['expts']: 793 emin, emax = expts 794 if emin == 'rand': 795 context.Emin = random.randrange(-1000, 0) 796 context.Emax = random.randrange(prec, 1000) 797 else: 798 context.Emin, context.Emax = emin, emax 799 if prec > context.Emax: continue 800 log(" prec: %d emin: %d emax: %d", 801 (context.prec, context.Emin, context.Emax)) 802 restr_range = 9999 if context.Emax > 9999 else context.Emax+99 803 for rounding in RoundModes: 804 context.rounding = rounding 805 context.capitals = random.randrange(2) 806 if spec['clamp'] == 'rand': 807 context.clamp = random.randrange(2) 808 else: 809 context.clamp = spec['clamp'] 810 exprange = context.c.Emax 811 testfunc(method, prec, exprange, restr_range, 812 spec['iter'], stat) 813 log(" result types: %s" % sorted([t for t in stat.items()])) 814 815def test_unary(method, prec, exp_range, restricted_range, itr, stat): 816 """Iterate a unary function through many test cases.""" 817 if method in UnaryRestricted: 818 exp_range = restricted_range 819 for op in all_unary(prec, exp_range, itr): 820 t = TestSet(method, op) 821 try: 822 if not convert(t): 823 continue 824 callfuncs(t) 825 verify(t, stat) 826 except VerifyError as err: 827 log(err) 828 829 if not method.startswith('__'): 830 for op in unary_optarg(prec, exp_range, itr): 831 t = TestSet(method, op) 832 try: 833 if not convert(t): 834 continue 835 callfuncs(t) 836 verify(t, stat) 837 except VerifyError as err: 838 log(err) 839 840def test_binary(method, prec, exp_range, restricted_range, itr, stat): 841 """Iterate a binary function through many test cases.""" 842 if method in BinaryRestricted: 843 exp_range = restricted_range 844 for op in all_binary(prec, exp_range, itr): 845 t = TestSet(method, op) 846 try: 847 if not convert(t): 848 continue 849 callfuncs(t) 850 verify(t, stat) 851 except VerifyError as err: 852 log(err) 853 854 if not method.startswith('__'): 855 for op in binary_optarg(prec, exp_range, itr): 856 t = TestSet(method, op) 857 try: 858 if not convert(t): 859 continue 860 callfuncs(t) 861 verify(t, stat) 862 except VerifyError as err: 863 log(err) 864 865def test_ternary(method, prec, exp_range, restricted_range, itr, stat): 866 """Iterate a ternary function through many test cases.""" 867 if method in TernaryRestricted: 868 exp_range = restricted_range 869 for op in all_ternary(prec, exp_range, itr): 870 t = TestSet(method, op) 871 try: 872 if not convert(t): 873 continue 874 callfuncs(t) 875 verify(t, stat) 876 except VerifyError as err: 877 log(err) 878 879 if not method.startswith('__'): 880 for op in ternary_optarg(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 890def 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(0, 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(0, 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 919def 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 933def test_from_float(method, prec, exprange, restricted_range, itr, stat): 934 """Iterate the __float__ method through many test cases.""" 935 for rounding in RoundModes: 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 949def 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 959def 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 976def 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 999if __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) 1101