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