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# Usage: python ctx-deccheck.py [--short|--medium|--long|--all] 31 32 33import cdecimal, decimal 34import sys, inspect 35import random 36from copy import copy 37from randdec import * 38 39 40EXIT_STATUS = 0 41# Python 2.5 bug in _dlog10. 42PY25_DLOG10_HAVE_WARNED = 0 43 44py_minor = sys.version_info[1] 45py_micro = sys.version_info[2] 46 47if py_minor == 5 and py_micro <= 4: 48 # decimal.py has additional bugs, not worth sorting them out individually. 49 sys.stderr.write(""" 50# 51# Error: ctx-deccheck2.py tests cdecimal against decimal.py. For several Python 52# versions, ctx-deccheck2.py suppresses known bugs in decimal.py in the output. 53# In versions 2.5.0 through 2.5.4, decimal.py has additional bugs which are 54# not discarded, so the output is too verbose to be useful. 55# 56# cdecimal should work fine, nevertheless it is recommended to upgrade 57# Python to version 2.5.5 or 2.6.6. 58# 59 60""") 61 sys.exit(1) 62 63 64# Translate symbols. 65deccond = { 66 cdecimal.Clamped: decimal.Clamped, 67 cdecimal.ConversionSyntax: decimal.ConversionSyntax, 68 cdecimal.DivisionByZero: decimal.DivisionByZero, 69 cdecimal.DivisionImpossible: decimal.InvalidOperation, 70 cdecimal.DivisionUndefined: decimal.DivisionUndefined, 71 cdecimal.Inexact: decimal.Inexact, 72 cdecimal.InvalidContext: decimal.InvalidContext, 73 cdecimal.InvalidOperation: decimal.InvalidOperation, 74 cdecimal.Overflow: decimal.Overflow, 75 cdecimal.Rounded: decimal.Rounded, 76 cdecimal.Subnormal: decimal.Subnormal, 77 cdecimal.Underflow: decimal.Underflow, 78} 79 80mpdcond = { 81 decimal.Clamped: cdecimal.Clamped, 82 decimal.ConversionSyntax: cdecimal.ConversionSyntax, 83 decimal.DivisionByZero: cdecimal.DivisionByZero, 84 decimal.InvalidOperation: cdecimal.DivisionImpossible, 85 decimal.DivisionUndefined: cdecimal.DivisionUndefined, 86 decimal.Inexact: cdecimal.Inexact, 87 decimal.InvalidContext: cdecimal.InvalidContext, 88 decimal.InvalidOperation: cdecimal.InvalidOperation, 89 decimal.Overflow: cdecimal.Overflow, 90 decimal.Rounded: cdecimal.Rounded, 91 decimal.Subnormal: cdecimal.Subnormal, 92 decimal.Underflow: cdecimal.Underflow 93} 94 95decround = { 96 cdecimal.ROUND_UP: decimal.ROUND_UP, 97 cdecimal.ROUND_DOWN: decimal.ROUND_DOWN, 98 cdecimal.ROUND_CEILING: decimal.ROUND_CEILING, 99 cdecimal.ROUND_FLOOR: decimal.ROUND_FLOOR, 100 cdecimal.ROUND_HALF_UP: decimal.ROUND_HALF_UP, 101 cdecimal.ROUND_HALF_DOWN: decimal.ROUND_HALF_DOWN, 102 cdecimal.ROUND_HALF_EVEN: decimal.ROUND_HALF_EVEN, 103 cdecimal.ROUND_05UP: decimal.ROUND_05UP 104} 105 106 107class Context(object): 108 """Provides a convenient way of syncing the cdecimal and decimal contexts""" 109 110 __slots__ = ['f', 'd'] 111 112 def __init__(self, mpdctx=cdecimal.getcontext()): 113 """Initialization is from the cdecimal context""" 114 self.f = mpdctx 115 self.d = decimal.getcontext() 116 self.d.prec = self.f.prec 117 self.d.Emin = self.f.Emin 118 self.d.Emax = self.f.Emax 119 self.d.rounding = decround[self.f.rounding] 120 self.d.capitals = self.f.capitals 121 self.settraps([sig for sig in self.f.traps if self.f.traps[sig]]) 122 self.setstatus([sig for sig in self.f.flags if self.f.flags[sig]]) 123 self.d._clamp = self.f._clamp 124 125 def getprec(self): 126 assert(self.f.prec == self.d.prec) 127 return self.f.prec 128 129 def setprec(self, val): 130 self.f.prec = val 131 self.d.prec = val 132 133 def getemin(self): 134 assert(self.f.Emin == self.d.Emin) 135 return self.f.Emin 136 137 def setemin(self, val): 138 self.f.Emin = val 139 self.d.Emin = val 140 141 def getemax(self): 142 assert(self.f.Emax == self.d.Emax) 143 return self.f.Emax 144 145 def setemax(self, val): 146 self.f.Emax = val 147 self.d.Emax = val 148 149 def getround(self): 150 return self.d.rounding 151 152 def setround(self, val): 153 self.f.rounding = val 154 self.d.rounding = decround[val] 155 156 def getcapitals(self): 157 assert(self.f.capitals == self.d.capitals) 158 return self.f.capitals 159 160 def setcapitals(self, val): 161 self.f.capitals = val 162 self.d.capitals = val 163 164 def getclamp(self): 165 assert(self.f._clamp == self.d._clamp) 166 return self.f._clamp 167 168 def setclamp(self, val): 169 self.f._clamp = val 170 self.d._clamp = val 171 172 prec = property(getprec, setprec) 173 Emin = property(getemin, setemin) 174 Emax = property(getemax, setemax) 175 rounding = property(getround, setround) 176 clamp = property(getclamp, setclamp) 177 capitals = property(getcapitals, setcapitals) 178 179 def clear_traps(self): 180 self.f.clear_traps() 181 for trap in self.d.traps: 182 self.d.traps[trap] = False 183 184 def clear_status(self): 185 self.f.clear_flags() 186 self.d.clear_flags() 187 188 def settraps(self, lst): # cdecimal signal list 189 self.clear_traps() 190 for signal in lst: 191 self.f.traps[signal] = True 192 self.d.traps[deccond[signal]] = True 193 194 def setstatus(self, lst): # cdecimal signal list 195 self.clear_status() 196 for signal in lst: 197 self.f.flags[signal] = True 198 self.d.flags[deccond[signal]] = True 199 200 def assert_eq_status(self): 201 """assert equality of cdecimal and decimal status""" 202 for signal in self.f.flags: 203 if signal == cdecimal.FloatOperation: 204 continue 205 if self.f.flags[signal] == (not self.d.flags[deccond[signal]]): 206 return False 207 return True 208 209 210# We don't want exceptions so that we can compare the status flags. 211context = Context() 212context.Emin = cdecimal.MIN_EMIN 213context.Emax = cdecimal.MAX_EMAX 214context.clear_traps() 215 216 217_exc_fmt = "\ 218cdecimal_sci: %s\n\ 219decimal_sci: %s\n\ 220cdecimal_eng: %s\n\ 221decimal_eng: %s\n" 222 223_exc_fmt_tuple = "\ 224cdecimal_tuple: %s\n\ 225decimal_tuple: %s\n" 226 227_exc_fmt_obj = "\ 228cdecimal: %s\n\ 229decimal: %s\n\n" 230 231class CdecException(ArithmeticError): 232 def __init__(self, result, funcname, operands, fctxstr, dctxstr): 233 self.errstring = "Error in %s(%s" % (funcname, operands[0].dec) 234 for op in operands[1:]: 235 if op: self.errstring += ", %s" % op.dec 236 self.errstring += "):\n\n" 237 if isinstance(result, cdec): 238 self.errstring += _exc_fmt % (str(result.mpd), 239 str(result.dec), 240 result.mpd.to_eng(), 241 result.dec.to_eng_string()) 242 mpd_tuple = result.mpd.as_tuple() 243 dec_tuple = result.dec.as_tuple() 244 if mpd_tuple != dec_tuple: 245 self.errstring += _exc_fmt_tuple % (str(mpd_tuple), 246 str(dec_tuple)) 247 else: 248 self.errstring += _exc_fmt_obj % (str(result[0]), str(result[1])) 249 self.errstring += "%s\n%s\n\n" % (fctxstr, dctxstr) 250 def __str__(self): 251 return self.errstring 252 253 254class dHandlerCdec: 255 """For cdec return values: 256 257 Handle known disagreements between decimal.py and cdecimal.so. 258 This is just a temporary measure against cluttered output. 259 Detection is crude and possibly unreliable.""" 260 261 def __init__(self): 262 self.logb_round_if_gt_prec = 0 263 self.ulpdiff = 0 264 self.powmod_zeros = 0 265 self.total_mag_nan = 0 266 self.quantize_status = 0 267 self.max_status = 0 268 self.maxctx = decimal.Context(Emax=10**18, Emin=-10**18) 269 270 def default(self, result, operands): 271 return False 272 273 def harrison_ulp(self, dec): 274 """Harrison ULP: ftp://ftp.inria.fr/INRIA/publication/publi-pdf/RR/RR-5504.pdf""" 275 a = dec.next_plus() 276 b = dec.next_minus() 277 return abs(a - b) 278 279 def standard_ulp(self, dec, prec): 280 return decimal._dec_from_triple(0, '1', dec._exp+len(dec._int)-prec) 281 282 def rounding_direction(self, x, mode): 283 """Determine the effective direction of the rounding when 284 the exact result x is rounded according to mode. 285 Return -1 for downwards, 0 for undirected, 1 for upwards, 286 2 for ROUND_05UP.""" 287 d = decimal 288 cmp = 1 if x.compare_total(d.Decimal("+0")) >= 0 else -1 289 290 if mode in (d.ROUND_HALF_EVEN, d.ROUND_HALF_UP, d.ROUND_HALF_DOWN): 291 return 0 292 elif mode == d.ROUND_CEILING: 293 return 1 294 elif mode == d.ROUND_FLOOR: 295 return -1 296 elif mode == d.ROUND_UP: 297 return cmp 298 elif mode == d.ROUND_DOWN: 299 return -cmp 300 elif mode == d.ROUND_05UP: 301 return 2 302 else: 303 raise ValueError("Unexpected rounding mode: %s" % mode) 304 305 def check_ulpdiff(self, exact, rounded): 306 # current precision 307 p = context.d.prec 308 309 # Convert infinities to the largest representable number + 1. 310 x = exact 311 if exact.is_infinite(): 312 x = decimal._dec_from_triple(exact._sign, '10', context.d.Emax) 313 y = rounded 314 if rounded.is_infinite(): 315 y = decimal._dec_from_triple(rounded._sign, '10', context.d.Emax) 316 317 # err = (rounded - exact) / ulp(rounded) 318 self.maxctx.prec = p * 2 319 t = self.maxctx.subtract(y, x) 320 if context.f._flags & cdecimal.DecClamped or \ 321 context.f._flags & cdecimal.DecUnderflow: 322 # The standard ulp does not work in Underflow territory. 323 ulp = self.harrison_ulp(y) 324 else: 325 ulp = self.standard_ulp(y, p) 326 # Error in ulps. 327 err = self.maxctx.divide(t, ulp) 328 329 d = decimal 330 dir = self.rounding_direction(x, context.d.rounding) 331 if dir == 0: 332 if d.Decimal("-0.6") < err < d.Decimal("0.6"): 333 return True 334 elif dir == 1: # directed, upwards 335 if d.Decimal("-0.1") < err < d.Decimal("1.1"): 336 return True 337 elif dir == -1: # directed, downwards 338 if d.Decimal("-1.1") < err < d.Decimal("0.1"): 339 return True 340 else: # ROUND_05UP 341 if d.Decimal("-1.1") < err < d.Decimal("1.1"): 342 return True 343 344 print("ulp: %s error: %s exact: %s mpd_rounded: %s" 345 % (ulp, err, exact, rounded)) 346 return False 347 348 def un_resolve_ulp(self, result, funcname, operands): 349 """Check if results of cdecimal's exp, ln and log10 functions are 350 within the allowed ulp ranges. This function is only called if 351 context.f._allcr is 0.""" 352 # "exact" result, double precision, half_even 353 self.maxctx.prec = context.d.prec * 2 354 op = operands[0].dec 355 exact = getattr(op, funcname)(context=self.maxctx) 356 357 # cdecimal's rounded result 358 s = str(result.mpd) 359 rounded = decimal.Decimal(s) 360 361 self.ulpdiff += 1 362 return self.check_ulpdiff(exact, rounded) 363 364 def bin_resolve_ulp(self, result, funcname, operands): 365 """Check if results of cdecimal's power function are within the 366 allowed ulp ranges.""" 367 # "exact" result, double precision, half_even 368 self.maxctx.prec = context.d.prec * 2 369 op1 = operands[0].dec 370 op2 = operands[1].dec 371 exact = getattr(op1, funcname)(op2, context=self.maxctx) 372 373 # cdecimal's rounded result 374 s = str(result.mpd) 375 rounded = decimal.Decimal(s) 376 377 self.ulpdiff += 1 378 return self.check_ulpdiff(exact, rounded) 379 380 def resolve_underflow(self, result): 381 """In extremely rare cases where the infinite precision result is just 382 below etiny, cdecimal does not set Subnormal/Underflow. Example: 383 384 setcontext(Context(prec=21, rounding=ROUND_UP, Emin=-55, Emax=85)) 385 Decimal("1.00000000000000000000000000000000000000000000000" 386 "0000000100000000000000000000000000000000000000000" 387 "0000000000000025").ln() 388 """ 389 if str(result.mpd) != str(result.dec): 390 return False # Results must be identical. 391 if context.f.flags[cdecimal.Rounded] and \ 392 context.f.flags[cdecimal.Inexact] and \ 393 context.d.flags[decimal.Rounded] and \ 394 context.d.flags[decimal.Inexact]: 395 return True # Subnormal/Underflow may be missing. 396 return False 397 398 def exp(self, result, operands): 399 if result.mpd.is_nan() or result.dec.is_nan(): 400 return False 401 if context.f._allcr: 402 return self.resolve_underflow(result) 403 return self.un_resolve_ulp(result, "exp", operands) 404 405 def log10(self, result, operands): 406 if result.mpd.is_nan() or result.dec.is_nan(): 407 return False 408 if context.f._allcr: 409 return self.resolve_underflow(result) 410 return self.un_resolve_ulp(result, "log10", operands) 411 412 def ln(self, result, operands): 413 if result.mpd.is_nan() or result.dec.is_nan(): 414 return False 415 if context.f._allcr: 416 return self.resolve_underflow(result) 417 return self.un_resolve_ulp(result, "ln", operands) 418 419 def __pow__(self, result, operands): 420 if operands[2] is not None: # three argument __pow__ 421 # issue7049: third arg must fit into precision 422 if (operands[0].mpd.is_zero() != operands[1].mpd.is_zero()): 423 if (result.mpd == 0 or result.mpd == 1) and result.dec.is_nan(): 424 if (not context.f.flags[cdecimal.InvalidOperation]) and \ 425 context.d.flags[decimal.InvalidOperation]: 426 self.powmod_zeros += 1 427 return True 428 # issue7049: ideal exponent 429 if decimal.Decimal(str(result.mpd)) == result.dec: 430 return True 431 elif result.mpd.is_nan() or result.dec.is_nan(): 432 return False 433 elif context.f.flags[cdecimal.Rounded] and \ 434 context.f.flags[cdecimal.Inexact] and \ 435 context.d.flags[decimal.Rounded] and \ 436 context.d.flags[decimal.Inexact]: 437 # decimal.py: correctly-rounded pow() 438 return self.bin_resolve_ulp(result, "__pow__", operands) 439 else: 440 return False 441 power = __pow__ 442 443 # Fixed in 2.7.2. 444 def plus(self, result, operands): 445 """special cases for zero/ROUND_FLOOR""" 446 if context.f.rounding == cdecimal.ROUND_FLOOR: 447 if operands[0].mpd.is_zero(): 448 return True 449 return False 450 minus = __neg__ = __pos__ = plus 451 452 if py_minor <= 6: 453 def rotate(self, result, operands): 454 """truncate excess digits before the operation""" 455 if len(operands[0].dec._int) > context.f.prec: 456 return True 457 return False 458 shift = rotate 459 460 def compare_total_mag(self, result, operands): 461 """fixed in Python2.6.?""" 462 if operands[0].mpd.is_nan() and operands[1].mpd.is_nan() and \ 463 abs(result.mpd) == 1 and abs(result.dec) == 1: 464 self.total_mag_nan += 1 465 return True 466 return False 467 compare_total = compare_total_mag 468 469 def logb(self, result, operands): 470 """fixed in Python2.6.?""" 471 if context.f.flags[cdecimal.Rounded] and \ 472 (not context.d.flags[decimal.Rounded]): 473 self.logb_round_if_gt_prec += 1 474 return True 475 return False 476 477 def max(self, result, operands): 478 """fixed in Python2.6.?""" 479 # hack, since is_nan() appears to be broken on the result 480 if result.mpd.is_normal() and 'sNaN' in result.dec.to_eng_string(): 481 return True 482 if context.f.flags[cdecimal.Subnormal] and \ 483 (not context.d.flags[decimal.Subnormal]): 484 self.max_status += 1 485 return True 486 return False 487 max_mag = max 488 min = max 489 min_mag = max 490 491 492class dHandlerObj(): 493 """For non-decimal return values: 494 495 Handle known disagreements between decimal.py and cdecimal.so. 496 Currently there are none.""" 497 498 def __init__(self): 499 pass 500 501 def default(self, result, operands): 502 return False 503 __eq__ = __ne__ = __ge__ = __gt__ = __le__ = __lt__ = \ 504 __repr__ = __str__ = default 505 506 if py_minor <= 6: 507 # Fixed in release26-maint, but a lot of distributed 508 # versions do not have the fix yet. 509 def is_normal(self, result, operands): 510 # Issue7099 511 if operands[0].mpd.is_normal(): 512 return True 513 return False 514 515 516dhandler_cdec = dHandlerCdec() 517def cdec_known_disagreement(result, funcname, operands): 518 return getattr(dhandler_cdec, funcname, dhandler_cdec.default)(result, operands) 519 520dhandler_obj = dHandlerObj() 521def obj_known_disagreement(result, funcname, operands): 522 return getattr(dhandler_obj, funcname, dhandler_obj.default)(result, operands) 523 524 525 526def verify(result, funcname, operands): 527 """Verifies that after operation 'funcname' with operand(s) 'operands' 528 result[0] and result[1] as well as the context flags have the same 529 values.""" 530 global EXIT_STATUS 531 if result[0] != result[1] or not context.assert_eq_status(): 532 if obj_known_disagreement(result, funcname, operands): 533 return # skip known disagreements 534 EXIT_STATUS = 1 535 raise CdecException(result, funcname, operands, 536 str(context.f), str(context.d)) 537 538 539class cdec(object): 540 """Joins cdecimal.so and decimal.py for redundant calculations with error 541 checking. Always calls the context methods of cdecimal and decimal. This 542 is not very clean, but an easy way of adapting deccheck.py for testing 543 context methods.""" 544 545 __slots__ = ['mpd', 'dec'] 546 547 def __new__(cls, value=None): 548 self = object.__new__(cls) 549 self.mpd = None 550 self.dec = None 551 if value is not None: 552 context.clear_status() 553 if isinstance(value, float): 554 self.mpd = context.f.create_decimal_from_float(value) 555 self.dec = context.d.create_decimal_from_float(value) 556 else: 557 self.mpd = context.f.create_decimal(value) 558 self.dec = context.d.create_decimal(value) 559 self.verify('__xnew__', (value,)) 560 return self 561 562 def verify(self, funcname, operands): 563 """Verifies that after operation 'funcname' with operand(s) 'operands' 564 self.mpd and self.dec as well as the context flags have the same 565 values.""" 566 global EXIT_STATUS 567 mpdstr = str(self.mpd) 568 decstr = str(self.dec) 569 mpdstr_eng = self.mpd.to_eng_string() 570 decstr_eng = self.dec.to_eng_string() 571 if mpdstr != decstr or mpdstr_eng != decstr_eng or \ 572 not context.assert_eq_status(): 573 if cdec_known_disagreement(self, funcname, operands): 574 return # skip known disagreements 575 EXIT_STATUS = 1 576 raise CdecException(self, funcname, operands, 577 str(context.f), str(context.d)) 578 579 def unaryfunc(self, funcname): 580 "unary function returning a cdec, uses the context methods" 581 context.clear_status() 582 c = cdec() 583 c.mpd = getattr(context.f, funcname)(self.mpd) 584 c.dec = getattr(context.d, funcname)(self.dec) 585 c.verify(funcname, (self,)) 586 return c 587 588 def obj_unaryfunc(self, funcname): 589 "unary function returning an object other than a cdec" 590 context.clear_status() 591 r_mpd = getattr(context.f, funcname)(self.mpd) 592 r_dec = getattr(context.d, funcname)(self.dec) 593 verify((r_mpd, r_dec), funcname, (self,)) 594 return r_mpd 595 596 def binaryfunc(self, other, funcname): 597 "binary function returning a cdec, uses the context methods" 598 context.clear_status() 599 c = cdec() 600 other_mpd = other_dec = other 601 if isinstance(other, cdec): 602 other_mpd = other.mpd 603 other_dec = other.dec 604 c.mpd = getattr(context.f, funcname)(self.mpd, other_mpd) 605 c.dec = getattr(context.d, funcname)(self.dec, other_dec) 606 c.verify(funcname, (self, other)) 607 return c 608 609 def obj_binaryfunc(self, other, funcname): 610 "binary function returning an object other than a cdec" 611 context.clear_status() 612 other_mpd = other_dec = other 613 if isinstance(other, cdec): 614 other_mpd = other.mpd 615 other_dec = other.dec 616 r_mpd = getattr(context.f, funcname)(self.mpd, other_mpd) 617 r_dec = getattr(context.d, funcname)(self.dec, other_dec) 618 verify((r_mpd, r_dec), funcname, (self, other)) 619 return r_mpd 620 621 def ternaryfunc(self, other, third, funcname): 622 "ternary function returning a cdec, uses the context methods" 623 context.clear_status() 624 c = cdec() 625 other_mpd = other_dec = other 626 if isinstance(other, cdec): 627 other_mpd = other.mpd 628 other_dec = other.dec 629 third_mpd = third_dec = third 630 if isinstance(third, cdec): 631 third_mpd = third.mpd 632 third_dec = third.dec 633 if funcname == 'power': 634 if (third is not None): 635 c.mpd = context.f.powmod(self.mpd, other_mpd, third_mpd) 636 else: 637 c.mpd = context.f.pow(self.mpd, other_mpd) 638 else: 639 c.mpd = getattr(context.f, funcname)(self.mpd, other_mpd, third_mpd) 640 c.dec = getattr(context.d, funcname)(self.dec, other_dec, third_dec) 641 c.verify(funcname, (self, other, third)) 642 return c 643 644 def __repr__(self): 645 self.obj_unaryfunc('__repr__') 646 return "cdec('" + str(self.mpd) + "')" 647 648 def __str__(self): 649 self.obj_unaryfunc('__str__') 650 return str(self.mpd) 651 652 def abs(self): 653 return self.unaryfunc('abs') 654 655 def add(self, other): 656 return self.binaryfunc(other, 'add') 657 658 def compare(self, other): 659 return self.binaryfunc(other, 'compare') 660 661 def compare_signal(self, other): 662 return self.binaryfunc(other, 'compare_signal') 663 664 def compare_total(self, other): 665 return self.binaryfunc(other, 'compare_total') 666 667 def compare_total_mag(self, other): 668 return self.binaryfunc(other, 'compare_total_mag') 669 670 def copy_abs(self): 671 return self.unaryfunc('copy_abs') 672 673 def copy_decimal(self): 674 return self.unaryfunc('copy_decimal') 675 676 def copy_negate(self): 677 return self.unaryfunc('copy_negate') 678 679 def copy_sign(self, other): 680 return self.binaryfunc(other, 'copy_sign') 681 682 def create_decimal(self): 683 return self.unaryfunc('create_decimal') 684 685 def divide(self, other): 686 return self.binaryfunc(other, 'divide') 687 688 def divide_int(self, other): 689 return self.binaryfunc(other, 'divide_int') 690 691 def divmod(self, other): 692 context.clear_status() 693 q = cdec() 694 r = cdec() 695 other_mpd = other_dec = other 696 if isinstance(other, cdec): 697 other_mpd = other.mpd 698 other_dec = other.dec 699 q.mpd, r.mpd = context.f.divmod(self.mpd, other_mpd) 700 q.dec, r.dec = context.d.divmod(self.dec, other_dec) 701 q.verify('divmod', (self, other)) 702 r.verify('divmod', (self, other)) 703 return (q, r) 704 705 def exp(self): 706 return self.unaryfunc('exp') 707 708 def fma(self, other, third): 709 return self.ternaryfunc(other, third, 'fma') 710 711 # imag 712 # invroot 713 714 def is_canonical(self): 715 return self.obj_unaryfunc('is_canonical') 716 717 def is_finite(self): 718 return self.obj_unaryfunc('is_finite') 719 720 def is_infinite(self): 721 return self.obj_unaryfunc('is_infinite') 722 723 def is_nan(self): 724 return self.obj_unaryfunc('is_nan') 725 726 def is_normal(self): 727 return self.obj_unaryfunc('is_normal') 728 729 def is_qnan(self): 730 return self.obj_unaryfunc('is_qnan') 731 732 def is_signed(self): 733 return self.obj_unaryfunc('is_signed') 734 735 def is_snan(self): 736 return self.obj_unaryfunc('is_snan') 737 738 def is_subnormal(self): 739 return self.obj_unaryfunc('is_subnormal') 740 741 def is_zero(self): 742 return self.obj_unaryfunc('is_zero') 743 744 def ln(self): 745 return self.unaryfunc('ln') 746 747 def log10(self): 748 global PY25_DLOG10_HAVE_WARNED 749 try: 750 return self.unaryfunc('log10') 751 except NameError: 752 if not PY25_DLOG10_HAVE_WARNED: 753 sys.stderr.write( 754 "\n\n*** warning: detected known bug in decimal.py: " 755 "replace div_nearest with _div_nearest in _dlog10().\n\n\n") 756 PY25_DLOG10_HAVE_WARNED = 1 757 return None 758 759 def logb(self): 760 return self.unaryfunc('logb') 761 762 def logical_and(self, other): 763 return self.binaryfunc(other, 'logical_and') 764 765 def logical_invert(self): 766 return self.unaryfunc('logical_invert') 767 768 def logical_or(self, other): 769 return self.binaryfunc(other, 'logical_or') 770 771 def logical_xor(self, other): 772 return self.binaryfunc(other, 'logical_xor') 773 774 def max(self, other): 775 return self.binaryfunc(other, 'max') 776 777 def max_mag(self, other): 778 return self.binaryfunc(other, 'max_mag') 779 780 def min(self, other): 781 return self.binaryfunc(other, 'min_mag') 782 783 def min_mag(self, other): 784 return self.binaryfunc(other, 'min_mag') 785 786 def minus(self): 787 return self.unaryfunc('minus') 788 789 def multiply(self, other): 790 return self.binaryfunc(other, 'multiply') 791 792 def next_minus(self): 793 return self.unaryfunc('next_minus') 794 795 def next_plus(self): 796 return self.unaryfunc('next_plus') 797 798 def next_toward(self, other): 799 return self.binaryfunc(other, 'next_toward') 800 801 def normalize(self): 802 return self.unaryfunc('normalize') 803 804 def number_class(self): 805 return self.obj_unaryfunc('number_class') 806 807 def plus(self): 808 return self.unaryfunc('plus') 809 810 def power(self, other, mod=None): 811 return self.ternaryfunc(other, mod, 'power') 812 813 # powmod: same as __pow__ or power with three arguments 814 815 def quantize(self, other): 816 return self.binaryfunc(other, 'quantize') 817 818 # real 819 # reduce: same as normalize 820 821 def remainder(self, other): 822 return self.binaryfunc(other, 'remainder') 823 824 def remainder_near(self, other): 825 return self.binaryfunc(other, 'remainder_near') 826 827 def rotate(self, other): 828 return self.binaryfunc(other, 'rotate') 829 830 def same_quantum(self, other): 831 return self.obj_binaryfunc(other, 'same_quantum') 832 833 def scaleb(self, other): 834 return self.binaryfunc(other, 'scaleb') 835 836 def shift(self, other): 837 return self.binaryfunc(other, 'shift') 838 839 # sign 840 841 def sqrt(self): 842 return self.unaryfunc('sqrt') 843 844 def subtract(self, other): 845 return self.binaryfunc(other, 'subtract') 846 847 def to_eng_string(self): 848 return self.obj_unaryfunc('to_eng_string') 849 850 def to_integral(self): 851 return self.unaryfunc('to_integral') 852 853 def to_integral_exact(self): 854 return self.unaryfunc('to_integral_exact') 855 856 def to_integral_value(self): 857 return self.unaryfunc('to_integral_value') 858 859 def to_sci_string(self): 860 return self.obj_unaryfunc('to_sci_string') 861 862 863def log(fmt, args=None): 864 if args: 865 sys.stdout.write(''.join((fmt, '\n')) % args) 866 else: 867 sys.stdout.write(''.join((str(fmt), '\n'))) 868 sys.stdout.flush() 869 870def test_unary(method, prec_lst, iter): 871 log("testing %s ...", method) 872 for prec in prec_lst: 873 log(" prec: %d", prec) 874 context.prec = prec 875 for rounding in sorted(decround): 876 context.rounding = rounding 877 rprec = 10**prec 878 exprange = context.f.Emax 879 if method in ['__int__', '__long__', '__trunc__', 'to_integral', \ 880 'to_integral_value', 'to_integral_value']: 881 exprange = 9999 882 for a in un_close_to_pow10(prec, exprange, iter): 883 try: 884 x = cdec(a) 885 getattr(x, method)() 886 except CdecException, err: 887 log(err) 888 for a in un_close_numbers(prec, exprange, -exprange, iter): 889 try: 890 x = cdec(a) 891 getattr(x, method)() 892 except CdecException, err: 893 log(err) 894 for a in un_incr_digits_tuple(prec, exprange, iter): 895 try: 896 x = cdec(a) 897 getattr(x, method)() 898 except CdecException, err: 899 log(err) 900 for i in range(1000): 901 try: 902 s = randdec(prec, exprange) 903 x = cdec(s) 904 getattr(x, method)() 905 except CdecException, err: 906 log(err) 907 except OverflowError: 908 pass 909 try: 910 s = randtuple(prec, exprange) 911 x = cdec(s) 912 getattr(x, method)() 913 except CdecException, err: 914 log(err) 915 except OverflowError: 916 pass 917 918def test_un_logical(method, prec_lst, iter): 919 log("testing %s ...", method) 920 for prec in prec_lst: 921 log(" prec: %d", prec) 922 context.prec = prec 923 for rounding in sorted(decround): 924 context.rounding = rounding 925 for a in logical_un_incr_digits(prec, iter): 926 try: 927 x = cdec(a) 928 getattr(x, method)() 929 except CdecException, err: 930 log(err) 931 for i in range(1000): 932 try: 933 s = randdec(prec, 999999) 934 x = cdec(s) 935 getattr(x, method)() 936 except CdecException, err: 937 log(err) 938 except OverflowError: 939 pass 940 941def test_binary(method, prec_lst, iter): 942 log("testing %s ...", method) 943 for prec in prec_lst: 944 log(" prec: %d", prec) 945 context.prec = prec 946 for rounding in sorted(decround): 947 context.rounding = rounding 948 exprange = context.f.Emax 949 if method in ['__pow__', '__rpow__', 'power']: 950 exprange = 9999 951 for a, b in bin_close_to_pow10(prec, exprange, iter): 952 try: 953 x = cdec(a) 954 y = cdec(b) 955 getattr(x, method)(y) 956 except CdecException, err: 957 log(err) 958 for a, b in bin_close_numbers(prec, exprange, -exprange, iter): 959 try: 960 x = cdec(a) 961 y = cdec(b) 962 getattr(x, method)(y) 963 except CdecException, err: 964 log(err) 965 for a, b in bin_incr_digits(prec, exprange, iter): 966 try: 967 x = cdec(a) 968 y = cdec(b) 969 getattr(x, method)(y) 970 except CdecException, err: 971 log(err) 972 for i in range(1000): 973 s1 = randdec(prec, exprange) 974 s2 = randdec(prec, exprange) 975 try: 976 x = cdec(s1) 977 y = cdec(s2) 978 getattr(x, method)(y) 979 except CdecException, err: 980 log(err) 981 982def test_bin_logical(method, prec_lst, iter): 983 log("testing %s ...", method) 984 for prec in prec_lst: 985 log(" prec: %d", prec) 986 context.prec = prec 987 for rounding in sorted(decround): 988 context.rounding = rounding 989 for a, b in logical_bin_incr_digits(prec, iter): 990 try: 991 x = cdec(a) 992 y = cdec(b) 993 getattr(x, method)(y) 994 except CdecException, err: 995 log(err) 996 for i in range(1000): 997 s1 = randdec(prec, 999999) 998 s2 = randdec(prec, 999999) 999 try: 1000 x = cdec(s1) 1001 y = cdec(s2) 1002 getattr(x, method)(y) 1003 except CdecException, err: 1004 log(err) 1005 1006def test_ternary(method, prec_lst, iter): 1007 log("testing %s ...", method) 1008 for prec in prec_lst: 1009 log(" prec: %d", prec) 1010 context.prec = prec 1011 for rounding in sorted(decround): 1012 context.rounding = rounding 1013 exprange = context.f.Emax 1014 if method in ['__pow__', 'power']: 1015 exprange = 9999 1016 for a, b, c in tern_close_numbers(prec, exprange, -exprange, iter): 1017 try: 1018 x = cdec(a) 1019 y = cdec(b) 1020 z = cdec(c) 1021 getattr(x, method)(y, z) 1022 except CdecException, err: 1023 log(err) 1024 for a, b, c in tern_incr_digits(prec, exprange, iter): 1025 try: 1026 x = cdec(a) 1027 y = cdec(b) 1028 z = cdec(c) 1029 getattr(x, method)(y, z) 1030 except CdecException, err: 1031 log(err) 1032 for i in range(1000): 1033 s1 = randdec(prec, 2*exprange) 1034 s2 = randdec(prec, 2*exprange) 1035 s3 = randdec(prec, 2*exprange) 1036 try: 1037 x = cdec(s1) 1038 y = cdec(s2) 1039 z = cdec(s3) 1040 getattr(x, method)(y, z) 1041 except CdecException, err: 1042 log(err) 1043 1044def test_from_float(prec_lst): 1045 log("testing create_decimal_from_float ...") 1046 for prec in prec_lst: 1047 log(" prec: %d", prec) 1048 context.prec = prec 1049 for rounding in sorted(decround): 1050 context.rounding = rounding 1051 exprange = 384 1052 for i in range(1000): 1053 intpart = str(random.randrange(100000000000000000000000000000000000000)) 1054 fracpart = str(random.randrange(100000000000000000000000000000000000000)) 1055 exp = str(random.randrange(-384, 384)) 1056 fstring = intpart + '.' + fracpart + 'e' + exp 1057 f = float(fstring) 1058 try: 1059 c = cdec(f) 1060 except CdecException, err: 1061 log(err) 1062 1063 1064if __name__ == '__main__': 1065 1066 import time 1067 1068 samples = 1 1069 iterations = 1 1070 1071 if '--short' in sys.argv: 1072 samples = 1 1073 iterations = 1 1074 elif '--medium' in sys.argv: 1075 samples = 1 1076 iterations = None 1077 elif '--long' in sys.argv: 1078 samples = 5 1079 iterations = None 1080 elif '--all' in sys.argv: 1081 samples = 100 1082 iterations = None 1083 1084 all_context_methods = set(dir(cdecimal.getcontext()) + dir(decimal.getcontext())) 1085 all_cdec_methods = [m for m in dir(cdec) if m in all_context_methods] 1086 untested_methods = [m for m in all_context_methods if not (m in all_cdec_methods)] 1087 1088 unary_methods = [] 1089 binary_methods = [] 1090 ternary_methods = [] 1091 for m in all_cdec_methods: 1092 try: 1093 l = len(inspect.getargspec(getattr(cdec, m))[0]) 1094 except TypeError: 1095 continue 1096 if l == 1: 1097 unary_methods.append(m) 1098 elif l == 2: 1099 binary_methods.append(m) 1100 elif l == 3: 1101 ternary_methods.append(m) 1102 else: 1103 raise ValueError((m, l)) 1104 1105 unary_methods.remove('__repr__') 1106 unary_methods.remove('__str__') 1107 binary_methods.remove('__new__') 1108 untested_methods.append('__repr__') 1109 untested_methods.append('__str__') 1110 untested_methods.append('__new__') 1111 untested_methods.remove('create_decimal_from_float') 1112 1113 binary_methods.append('power') 1114 1115 untested_methods.sort() 1116 unary_methods.sort() 1117 binary_methods.sort() 1118 ternary_methods.sort() 1119 1120 1121 x = int(time.time()) 1122 random.seed(x) 1123 log("\nRandom seed: %d\n\n", x) 1124 log("Skipping tests: \n\n%s\n", untested_methods) 1125 1126 1127 for method in unary_methods: 1128 prec_lst = sorted(random.sample(range(1, 101), samples)) 1129 test_unary(method, prec_lst, iterations) 1130 1131 for method in binary_methods: 1132 prec_lst = sorted(random.sample(range(1, 101), samples)) 1133 test_binary(method, prec_lst, iterations) 1134 1135 for method in ternary_methods: 1136 prec_lst = sorted(random.sample(range(1, 101), samples)) 1137 test_ternary(method, prec_lst, iterations) 1138 1139 prec_lst = sorted(random.sample(range(1, 101), samples)) 1140 test_un_logical('logical_invert', prec_lst, iterations) 1141 1142 for method in ['logical_and', 'logical_or', 'logical_xor']: 1143 prec_lst = sorted(random.sample(range(1, 101), samples)) 1144 test_bin_logical(method, prec_lst, iterations) 1145 1146 if py_minor >= 7: 1147 prec_lst = sorted(random.sample(range(1, 101), samples)) 1148 test_from_float(prec_lst) 1149 1150 1151 sys.exit(EXIT_STATUS) 1152