c 20082012 stefan krah redistribution and use in source and binary forms with or without modification are permitted provided that the following conditions are met 1 redistributions of source code must retain the above notice this list of conditions and the following disclaimer 2 redistributions in binary form must reproduce the above notice this list of conditions and the following disclaimer in the documentation andor other materials provided with the distribution this software is provided by the and contributors as is and any express or implied warranties including but not limited to the implied warranties of merchantability and fitness for a particular purpose are disclaimed in no event shall the or contributors be liable for any direct indirect incidental special exemplary or consequential damages including but not limited to procurement of substitute goods or services loss of use data or profits or business interruption however caused and on any theory of liability whether in contract strict liability or tort including negligence or otherwise arising in any way out of the use of this software even if advised of the possibility of such damage usage python deccheck py shortmediumlongall contains all categories of decimal methods plain unary unary with optional context unary with optional rounding mode and context plain binary binary with optional context plain ternary ternary with optional context special properties contains all categories of context methods the nary classification applies to the number of decimal arguments plain nullary plain unary plain binary plain ternary special functions that set no context flags but whose result can differ depending on prec emin and emax functions that require a restricted exponent range for reasonable runtimes unified context translate symbols provides a convenient way of syncing the c and p contexts slots c p def initself cctxnone pctxnone lst c signal list self cleartraps for signal in lst self c trapssignal true self p trapscondmapsignal true def setstatusself lst assert equality of c and p status for signal in self c flags if self c flagssignal not self p flagscondmapsignal return false return true we don t want exceptions so that we can compare the status flags context context context emin c minemin context emax c maxemax context cleartraps when creating decimals decimal is ultimately limited by the maximum context values we emulate this restriction for decimal py maxcontext p context precc maxprec eminc minemin emaxc maxemax roundingp roundhalfup capitals1 maxcontext clamp 0 def restricteddecimalvalue maxcontext traps copycontext p traps maxcontext clearflags if isinstancevalue str value value strip dec maxcontext createdecimalvalue if maxcontext flagsp inexact or maxcontext flagsp rounded or maxcontext flagsp clamped or maxcontext flagsp invalidoperation return context p raiseerrorp invalidoperation if maxcontext flagsp floatoperation context p flagsp floatoperation true return dec testset organize data and events during a single test case class restrictedlistlist a testset contains the original input operands converted operands python exceptions that occurred either during conversion or during execution of the actual function and the final results for safety most attributes are lists that only support the append operation if a function name is prefixed with context the corresponding context method is called if the above results are exact unrounded and not clamped repeat the operation with a maxcontext to ensure that huge intermediate values do not cause a memoryerror skiphandler skip known discrepancies handle known discrepancies between decimal py and decimal so these are either ulp differences in the power function or extremely minor issues def initself self ulpdiff 0 self powmodzeros 0 self maxctx p contextemax1018 emin1018 def defaultself t return false ge gt le lt ne eq default reduce format repr str default def harrisonulpself dec determine the effective direction of the rounding when the exact result x is rounded according to mode return 1 for downwards 0 for undirected 1 for upwards 2 for round05up cmp 1 if x comparetotalp decimal0 0 else 1 if mode in p roundhalfeven p roundhalfup p roundhalfdown return 0 elif mode p roundceiling return 1 elif mode p roundfloor return 1 elif mode p roundup return cmp elif mode p rounddown return cmp elif mode p round05up return 2 else raise valueerrorunexpected rounding mode s mode def checkulpdiffself exact rounded current precision p context p prec convert infinities to the largest representable number 1 x exact if exact isinfinite x decfromtripleexact sign 10 context p emax y rounded if rounded isinfinite y decfromtriplerounded sign 10 context p emax err rounded exact ulprounded self maxctx prec p 2 t self maxctx subtracty x if context c flagsc clamped or context c flagsc underflow the standard ulp does not work in underflow territory ulp self harrisonulpy else ulp self standardulpy p error in ulps err self maxctx dividet ulp dir self roundingdirectionx context p rounding if dir 0 if p decimal0 6 err p decimal0 6 return true elif dir 1 directed upwards if p decimal0 1 err p decimal1 1 return true elif dir 1 directed downwards if p decimal1 1 err p decimal0 1 return true else round05up if p decimal1 1 err p decimal1 1 return true printulp s error s exact s crounded s ulp err exact rounded return false def binresolveulpself t nans are beyond repair exact result double precision halfeven decimal s rounded result correct rounding in extremely rare cases where the infinite precision result is just below etiny cdecimal does not set subnormalunderflow example setcontextcontextprec21 roundingroundup emin55 emax85 decimal1 00000000000000000000000000000000000000000000000 0000000100000000000000000000000000000000000000000 0000000000000025 ln resolve underflow or ulp difference return self resolveunderflowt def log10self t resolve underflow or ulp difference return self resolveunderflowt def powself t technicalities nan comparison in the verify function obviously gives an incorrect answer nan nan false if t cop0 isnan and t pop0 isnan return true return false complex float def raddself t various exception decimal 1 round100000000000000000000000000 should it really be invalidoperation if t rc is none and t rp isnan return true return false shandler skiphandler def skiperrort return getattrshandler t funcname shandler defaultt handling verification errors class verifyerrorexception main testing functions the procedure is always t is the testset convertt initialize the testset as necessary return 0 for early abortion e g if a typeerror occurs during conversion there is nothing to test return 1 for continuing with the test case callfuncst call the relevant function for each implementation and record the results in the testset verifyt verify the results if verification fails details are printed to stdout t is the testset at this stage the testset contains a tuple of operands t op of various types for decimal methods the first operand self is always converted to decimal if convstr is true string operands are converted as well context operands are of type deccheck context rounding mode operands are given as a tuple c rounding p rounding other types float int etc are left unchanged nothing to test the exceptions in the maxcontext operation can legitimately differ only test that maxex implies cex t is the testset at this stage the testset contains operand lists t cop and t pop for the c and python versions of decimal for decimal methods the first operands are of type c decimal and p decimal respectively the remaining operands can have various types for context methods all operands can have any type t rc and t rp are the results of the operation if the above results are exact unrounded normal etc repeat the operation with a maxcontext to ensure that huge intermediate values do not cause a memoryerror t is the testset at this stage the testset contains the following tuples t op original operands t cop c decimal operands see convert for details t pop p decimal operands see convert for details t rc c result t rp python result t rc and t rp can have various types general case both results are decimals results from e g divmod can only be compared as strings the return value lists must be equal the python exception lists typeerror etc must be equal the context flags must be equal nan payloads etc depend on precision and clamp the return value lists must be equal the python exception lists typeerror etc must be equal the context flags must be equal main test loops testmethodmethod testspecs testfunc loop through various context settings the degree of thoroughness is determined by testspec for each setting call testfunc generally testfunc itself a loop iterating through many test cases generated by the functions in randdec py testnarymethod prec exprange restrictedrange itr stat testunary testbinary and testternary are the main test functions passed to testmethod they deal with the regular cases the thoroughness of testing is determined by itr prec exprange and restrictedrange are passed to the testgenerating functions and limit the generated values in some cases for reasonable run times a maximum exponent of 9999 is required the stat parameter is passed down to the verify function which records statistics for the result values iterate a test function through many context settings logtesting s method stat defaultdictint for spec in testspecs if samples in spec spec prec sortedrandom samplerange1 101 spec samples for prec in spec prec context prec prec for expts in spec expts emin emax expts if emin rand context emin random randrange1000 0 context emax random randrangeprec 1000 else context emin context emax emin emax if prec context emax continue log prec d emin d emax d context prec context emin context emax restrrange 9999 if context emax 9999 else context emax99 for rounding in roundmodes context rounding rounding context capitals random randrange2 if spec clamp rand context clamp random randrange2 else context clamp spec clamp exprange context c emax testfuncmethod prec exprange restrrange spec iter stat log result types s sortedt for t in stat items def testunarymethod prec exprange restrictedrange itr stat iterate a binary function through many test cases if method in binaryrestricted exprange restrictedrange for op in allbinaryprec exprange itr t testsetmethod op try if not convertt continue callfuncst verifyt stat except verifyerror as err logerr if not method startswith for op in binaryoptargprec exprange itr t testsetmethod op try if not convertt continue callfuncst verifyt stat except verifyerror as err logerr def testternarymethod prec exprange restrictedrange itr stat iterate the format method through many test cases for op in allunaryprec exprange itr fmt1 randformatchrrandom randrange0 128 eeggn fmt2 randlocale for fmt in fmt1 fmt2 fmtop op0 fmt t testsetmethod fmtop try if not convertt convstrfalse continue callfuncst verifyt stat except verifyerror as err logerr for op in allunaryprec 9999 itr fmt1 randformatchrrandom randrange0 128 ff fmt2 randlocale for fmt in fmt1 fmt2 fmtop op0 fmt t testsetmethod fmtop try if not convertt convstrfalse continue callfuncst verifyt stat except verifyerror as err logerr def testroundmethod prec exprange restrictedrange itr stat iterate the float method through many test cases for rounding in roundmodes context rounding rounding for i in range1000 f randfloat op f if method startswithcontext else snan f t testsetmethod op try if not convertt continue callfuncst verifyt stat except verifyerror as err logerr def randcontextexprange c contextc context p context c emax random randrange1 exprange1 c emin random randrangeexprange 0 maxprec 100 if c emax 100 else c emax c prec random randrange1 maxprec1 c clamp random randrange2 c cleartraps return c def testquantizeapimethod prec exprange restrictedrange itr stat determine untested conly and pythononly attributes uncomment print lines for debugging cattr setdirccls pattr setdirpcls intersect cattr pattr funcdict conly tuplesortedcattrintersect funcdict ponly tuplesortedpattrintersect tested set for lst in funcdict values for v in lst v v replacecontext if ccls c context else v tested addv funcdict untested tuplesortedintersecttested for key in untested conly ponly s context if ccls c context else decimal printns s ns s key funcdictkey if name main parser argparse argumentparserprogdeccheck py group parser addmutuallyexclusivegroup group addargument short dest time actionstoreconst const short default short helpshort test default group addargument medium dest time actionstoreconst const medium default short helpmedium test reasonable run time group addargument long dest time actionstoreconst const long default short helplong test long run time group addargument all dest time actionstoreconst const all default short helpall tests excessive run time group parser addmutuallyexclusivegroup group addargument single dest single nargs1 defaultfalse metavartest helprun a single test group addargument multicore dest multicore actionstoretrue defaultfalse helpuse all available cores args parser parseargs assert args single is false or args multicore is false if args single args single args single0 set up the testspecs list a testspec is simply a dictionary that determines the amount of different contexts that testmethod will generate baseexpts c minemin c maxemax if c maxemax 999999999999999999 baseexpts append999999999 999999999 basic contexts base expts baseexpts prec clamp rand iter none samples none contexts with small values for prec emin emax small prec 1 2 3 4 5 expts 1 1 2 2 3 3 4 4 5 5 clamp rand iter none ieee interchange format ieee decimal32 prec 7 expts 95 96 clamp 1 iter none decimal64 prec 16 expts 383 384 clamp 1 iter none decimal128 prec 34 expts 6143 6144 clamp 1 iter none if args time medium base expts append rand rand 5 random precisions base samples 5 testspecs small ieee base elif args time long base expts append rand rand 10 random precisions base samples 10 testspecs small ieee base elif args time all base expts append rand rand all precisions in 1 100 base samples 100 testspecs small ieee base else short randieee random choiceieee base iter small iter randieee iter 1 1 random precision and exponent pair base samples 1 base expts random choicebaseexpts 1 random precision and exponent pair prec random randrange1 6 small prec prec small expts prec prec testspecs small randieee base checkuntestedfunctions c decimal p decimal checkuntestedcontextfunctions c context p context if args multicore q queue elif args single lograndom seed d randseed else lognnrandom seed dnn randseed foundmethod false def dosinglemethod f global foundmethod if args multicore q putmethod elif not args single or args single method foundmethod true f decimal methods for method in functions unary functions unaryctx functions unaryrndctx dosinglemethod lambda testmethodmethod testspecs testunary for method in functions binary functions binaryctx dosinglemethod lambda testmethodmethod testspecs testbinary for method in functions ternary functions ternaryctx name powmod if method pow else method dosinglename lambda testmethodmethod testspecs testternary dosingle format lambda testmethod format testspecs testformat dosingle round lambda testmethod round testspecs testround dosingle fromfloat lambda testmethod fromfloat testspecs testfromfloat dosingle quantizeapi lambda testmethod quantize testspecs testquantizeapi context methods for method in contextfunctions unary dosinglemethod lambda testmethodmethod testspecs testunary for method in contextfunctions binary dosinglemethod lambda testmethodmethod testspecs testbinary for method in contextfunctions ternary name context powmod if method context power else method dosinglename lambda testmethodmethod testspecs testternary dosingle context createdecimalfromfloat lambda testmethod context createdecimalfromfloat testspecs testfromfloat if args multicore error event writelock lock def writeoutputout returncode if returncode 0 error set with writelock sys stdout buffer writeout bn sys stdout buffer flush def tfunc while not error isset try test q getblockfalse timeout1 except empty return cmd sys executable deccheck py s args time single test p subprocess popencmd stdoutpipe stderrstdout out p communicate writeoutputout p returncode n os processcpucount t n none for i in rangen ti threadtargettfunc ti start for i in rangen ti join sys exit1 if error isset else 0 elif args single if not foundmethod lognerror cannot find method s args single exitstatus 1 sys exitexitstatus else sys exitexitstatus c 2008 2012 stefan krah redistribution and use in source and binary forms with or without modification are permitted provided that the following conditions are met 1 redistributions of source code must retain the above notice this list of conditions and the following disclaimer 2 redistributions in binary form must reproduce the above notice this list of conditions and the following disclaimer in the documentation and or other materials provided with the distribution this software is provided by the and contributors as is and any express or implied warranties including but not limited to the implied warranties of merchantability and fitness for a particular purpose are disclaimed in no event shall the or contributors be liable for any direct indirect incidental special exemplary or consequential damages including but not limited to procurement of substitute goods or services loss of use data or profits or business interruption however caused and on any theory of liability whether in contract strict liability or tort including negligence or otherwise arising in any way out of the use of this software even if advised of the possibility of such damage usage python deccheck py short medium long all contains all categories of decimal methods plain unary unary with optional context unary with optional rounding mode and context plain binary binary with optional context plain ternary ternary with optional context special properties contains all categories of context methods the n ary classification applies to the number of decimal arguments plain nullary plain unary plain binary plain ternary special functions that set no context flags but whose result can differ depending on prec emin and emax functions that require a restricted exponent range for reasonable runtimes unified context translate symbols provides a convenient way of syncing the c and p contexts initialization is from the c context lst c signal list lst c signal list assert equality of c and p status we don t want exceptions so that we can compare the status flags when creating decimals _decimal is ultimately limited by the maximum context values we emulate this restriction for decimal py testset organize data and events during a single test case list that can only be modified by appending items a testset contains the original input operands converted operands python exceptions that occurred either during conversion or during execution of the actual function and the final results for safety most attributes are lists that only support the append operation if a function name is prefixed with context the corresponding context method is called raw operand tuple context used for the operation converted c decimal operands python exceptions for c decimal c decimal results converted p decimal operands python exceptions for p decimal p decimal results if the above results are exact unrounded and not clamped repeat the operation with a maxcontext to ensure that huge intermediate values do not cause a memoryerror converted c decimal operands python exceptions for c decimal c decimal results skiphandler skip known discrepancies handle known discrepancies between decimal py and _decimal so these are either ulp differences in the power function or extremely minor issues ftp ftp inria fr inria publication publi pdf rr rr 5504 pdf determine the effective direction of the rounding when the exact result x is rounded according to mode return 1 for downwards 0 for undirected 1 for upwards 2 for round_05up current precision convert infinities to the largest representable number 1 err rounded exact ulp rounded the standard ulp does not work in underflow territory error in ulps directed upwards directed downwards round_05up check if results of _decimal s power function are within the allowed ulp ranges nans are beyond repair exact result double precision half_even _decimal s rounded result correct rounding in extremely rare cases where the infinite precision result is just below etiny cdecimal does not set subnormal underflow example setcontext context prec 21 rounding round_up emin 55 emax 85 decimal 1 00000000000000000000000000000000000000000000000 0000000100000000000000000000000000000000000000000 0000000000000025 ln results must be identical subnormal underflow may be missing resolve underflow or ulp difference resolve underflow or ulp difference resolve underflow or ulp difference always calls the resolve function c decimal does not have correct rounding for the power function technicalities nan comparison in the verify function obviously gives an incorrect answer nan nan false decimal py gives precedence to the first nan this is not important as __radd__ will not be called for two decimal arguments various exception decimal 1 __round__ 100000000000000000000000000 should it really be invalidoperation handling verification errors verification failed main testing functions the procedure is always t is the testset convert t initialize the testset as necessary return 0 for early abortion e g if a typeerror occurs during conversion there is nothing to test return 1 for continuing with the test case callfuncs t call the relevant function for each implementation and record the results in the testset verify t verify the results if verification fails details are printed to stdout t is the testset at this stage the testset contains a tuple of operands t op of various types for decimal methods the first operand self is always converted to decimal if convstr is true string operands are converted as well context operands are of type deccheck context rounding mode operands are given as a tuple c rounding p rounding other types float int etc are left unchanged nothing to test the exceptions in the maxcontext operation can legitimately differ only test that maxex implies cex t is the testset at this stage the testset contains operand lists t cop and t pop for the c and python versions of decimal for decimal methods the first operands are of type c decimal and p decimal respectively the remaining operands can have various types for context methods all operands can have any type t rc and t rp are the results of the operation if the above results are exact unrounded normal etc repeat the operation with a maxcontext to ensure that huge intermediate values do not cause a memoryerror results are padded to context prec if context clamp 1 another context is used t is the testset at this stage the testset contains the following tuples t op original operands t cop c decimal operands see convert for details t pop p decimal operands see convert for details t rc c result t rp python result t rc and t rp can have various types general case both results are decimals results from e g __divmod__ can only be compared as strings the return value lists must be equal the python exception lists typeerror etc must be equal the context flags must be equal nan payloads etc depend on precision and clamp the return value lists must be equal the python exception lists typeerror etc must be equal the context flags must be equal main test loops test_method method testspecs testfunc loop through various context settings the degree of thoroughness is determined by testspec for each setting call testfunc generally testfunc itself a loop iterating through many test cases generated by the functions in randdec py test_n ary method prec exp_range restricted_range itr stat test_unary test_binary and test_ternary are the main test functions passed to test_method they deal with the regular cases the thoroughness of testing is determined by itr prec exp_range and restricted_range are passed to the test generating functions and limit the generated values in some cases for reasonable run times a maximum exponent of 9999 is required the stat parameter is passed down to the verify function which records statistics for the result values iterate a test function through many context settings iterate a unary function through many test cases iterate a binary function through many test cases iterate a ternary function through many test cases iterate the __format__ method through many test cases iterate the __round__ method through many test cases iterate the __float__ method through many test cases iterate the quantize method through many test cases using the optional arguments determine untested c only and python only attributes uncomment print lines for debugging for key in untested c_only p_only s context if c_cls c context else decimal print n s s n s s key funcdict key set up the testspecs list a testspec is simply a dictionary that determines the amount of different contexts that test_method will generate basic contexts contexts with small values for prec emin emax ieee interchange format decimal32 decimal64 decimal128 5 random precisions 10 random precisions all precisions in 1 100 short 1 random precision and exponent pair 1 random precision and exponent pair decimal methods context methods
import random
import time

RANDSEED = int(time.time())
random.seed(RANDSEED)

import sys
import os
from copy import copy
from collections import defaultdict

import argparse
import subprocess
from subprocess import PIPE, STDOUT
from queue import Queue, Empty
from threading import Thread, Event, Lock

from test.support.import_helper import import_fresh_module
from randdec import randfloat, all_unary, all_binary, all_ternary
from randdec import unary_optarg, binary_optarg, ternary_optarg
from formathelper import rand_format, rand_locale
from _pydecimal import _dec_from_triple

C = import_fresh_module('decimal', fresh=['_decimal'])
P = import_fresh_module('decimal', blocked=['_decimal'])
EXIT_STATUS = 0



Functions = {
    
    'unary': (
        '__abs__', '__bool__', '__ceil__', '__complex__', '__copy__',
        '__floor__', '__float__', '__hash__', '__int__', '__neg__',
        '__pos__', '__reduce__', '__repr__', '__str__', '__trunc__',
        'adjusted', 'as_integer_ratio', 'as_tuple', 'canonical', 'conjugate',
        'copy_abs', 'copy_negate', 'is_canonical', 'is_finite', 'is_infinite',
        'is_nan', 'is_qnan', 'is_signed', 'is_snan', 'is_zero', 'radix'
    ),
    
    'unary_ctx': (
        'exp', 'is_normal', 'is_subnormal', 'ln', 'log10', 'logb',
        'logical_invert', 'next_minus', 'next_plus', 'normalize',
        'number_class', 'sqrt', 'to_eng_string'
    ),
    
    'unary_rnd_ctx': ('to_integral', 'to_integral_exact', 'to_integral_value'),
    
    'binary': (
        '__add__', '__divmod__', '__eq__', '__floordiv__', '__ge__', '__gt__',
        '__le__', '__lt__', '__mod__', '__mul__', '__ne__', '__pow__',
        '__radd__', '__rdivmod__', '__rfloordiv__', '__rmod__', '__rmul__',
        '__rpow__', '__rsub__', '__rtruediv__', '__sub__', '__truediv__',
        'compare_total', 'compare_total_mag', 'copy_sign', 'quantize',
        'same_quantum'
    ),
    
    'binary_ctx': (
        'compare', 'compare_signal', 'logical_and', 'logical_or', 'logical_xor',
        'max', 'max_mag', 'min', 'min_mag', 'next_toward', 'remainder_near',
        'rotate', 'scaleb', 'shift'
    ),
    
    'ternary': ('__pow__',),
    
    'ternary_ctx': ('fma',),
    
    'special': ('__format__', '__reduce_ex__', '__round__', 'from_float',
                'quantize'),
    
    'property': ('real', 'imag')
}



ContextFunctions = {
    
    'nullary': ('context.__hash__', 'context.__reduce__', 'context.radix'),
    
    'unary': ('context.abs', 'context.canonical', 'context.copy_abs',
              'context.copy_decimal', 'context.copy_negate',
              'context.create_decimal', 'context.exp', 'context.is_canonical',
              'context.is_finite', 'context.is_infinite', 'context.is_nan',
              'context.is_normal', 'context.is_qnan', 'context.is_signed',
              'context.is_snan', 'context.is_subnormal', 'context.is_zero',
              'context.ln', 'context.log10', 'context.logb',
              'context.logical_invert', 'context.minus', 'context.next_minus',
              'context.next_plus', 'context.normalize', 'context.number_class',
              'context.plus', 'context.sqrt', 'context.to_eng_string',
              'context.to_integral', 'context.to_integral_exact',
              'context.to_integral_value', 'context.to_sci_string'
    ),
    
    'binary': ('context.add', 'context.compare', 'context.compare_signal',
               'context.compare_total', 'context.compare_total_mag',
               'context.copy_sign', 'context.divide', 'context.divide_int',
               'context.divmod', 'context.logical_and', 'context.logical_or',
               'context.logical_xor', 'context.max', 'context.max_mag',
               'context.min', 'context.min_mag', 'context.multiply',
               'context.next_toward', 'context.power', 'context.quantize',
               'context.remainder', 'context.remainder_near', 'context.rotate',
               'context.same_quantum', 'context.scaleb', 'context.shift',
               'context.subtract'
    ),
    
    'ternary': ('context.fma', 'context.power'),
    
    'special': ('context.__reduce_ex__', 'context.create_decimal_from_float')
}



MaxContextSkip = ['is_normal', 'is_subnormal', 'logical_invert', 'next_minus',
                  'next_plus', 'number_class', 'logical_and', 'logical_or',
                  'logical_xor', 'next_toward', 'rotate', 'shift']


UnaryRestricted = [
  '__ceil__', '__floor__', '__int__', '__trunc__',
  'as_integer_ratio', 'to_integral', 'to_integral_value'
]

BinaryRestricted = ['__round__']

TernaryRestricted = ['__pow__', 'context.power']







CondMap = {
        C.Clamped:             P.Clamped,
        C.ConversionSyntax:    P.ConversionSyntax,
        C.DivisionByZero:      P.DivisionByZero,
        C.DivisionImpossible:  P.InvalidOperation,
        C.DivisionUndefined:   P.DivisionUndefined,
        C.Inexact:             P.Inexact,
        C.InvalidContext:      P.InvalidContext,
        C.InvalidOperation:    P.InvalidOperation,
        C.Overflow:            P.Overflow,
        C.Rounded:             P.Rounded,
        C.Subnormal:           P.Subnormal,
        C.Underflow:           P.Underflow,
        C.FloatOperation:      P.FloatOperation,
}

RoundModes = [C.ROUND_UP, C.ROUND_DOWN, C.ROUND_CEILING, C.ROUND_FLOOR,
              C.ROUND_HALF_UP, C.ROUND_HALF_DOWN, C.ROUND_HALF_EVEN,
              C.ROUND_05UP]


class Context(object):
    

    __slots__ = ['c', 'p']

    def __init__(self, c_ctx=None, p_ctx=None):
        
        self.c = C.getcontext() if c_ctx is None else c_ctx
        self.p = P.getcontext() if p_ctx is None else p_ctx
        self.p.prec = self.c.prec
        self.p.Emin = self.c.Emin
        self.p.Emax = self.c.Emax
        self.p.rounding = self.c.rounding
        self.p.capitals = self.c.capitals
        self.settraps([sig for sig in self.c.traps if self.c.traps[sig]])
        self.setstatus([sig for sig in self.c.flags if self.c.flags[sig]])
        self.p.clamp = self.c.clamp

    def __str__(self):
        return str(self.c) + '\n' + str(self.p)

    def getprec(self):
        assert(self.c.prec == self.p.prec)
        return self.c.prec

    def setprec(self, val):
        self.c.prec = val
        self.p.prec = val

    def getemin(self):
        assert(self.c.Emin == self.p.Emin)
        return self.c.Emin

    def setemin(self, val):
        self.c.Emin = val
        self.p.Emin = val

    def getemax(self):
        assert(self.c.Emax == self.p.Emax)
        return self.c.Emax

    def setemax(self, val):
        self.c.Emax = val
        self.p.Emax = val

    def getround(self):
        assert(self.c.rounding == self.p.rounding)
        return self.c.rounding

    def setround(self, val):
        self.c.rounding = val
        self.p.rounding = val

    def getcapitals(self):
        assert(self.c.capitals == self.p.capitals)
        return self.c.capitals

    def setcapitals(self, val):
        self.c.capitals = val
        self.p.capitals = val

    def getclamp(self):
        assert(self.c.clamp == self.p.clamp)
        return self.c.clamp

    def setclamp(self, val):
        self.c.clamp = val
        self.p.clamp = val

    prec = property(getprec, setprec)
    Emin = property(getemin, setemin)
    Emax = property(getemax, setemax)
    rounding = property(getround, setround)
    clamp = property(getclamp, setclamp)
    capitals = property(getcapitals, setcapitals)

    def clear_traps(self):
        self.c.clear_traps()
        for trap in self.p.traps:
            self.p.traps[trap] = False

    def clear_status(self):
        self.c.clear_flags()
        self.p.clear_flags()

    def settraps(self, lst):
        
        self.clear_traps()
        for signal in lst:
            self.c.traps[signal] = True
            self.p.traps[CondMap[signal]] = True

    def setstatus(self, lst):
        
        self.clear_status()
        for signal in lst:
            self.c.flags[signal] = True
            self.p.flags[CondMap[signal]] = True

    def assert_eq_status(self):
        
        for signal in self.c.flags:
            if self.c.flags[signal] == (not self.p.flags[CondMap[signal]]):
                return False
        return True



context = Context()
context.Emin = C.MIN_EMIN
context.Emax = C.MAX_EMAX
context.clear_traps()



maxcontext = P.Context(
    prec=C.MAX_PREC,
    Emin=C.MIN_EMIN,
    Emax=C.MAX_EMAX,
    rounding=P.ROUND_HALF_UP,
    capitals=1
)
maxcontext.clamp = 0

def RestrictedDecimal(value):
    maxcontext.traps = copy(context.p.traps)
    maxcontext.clear_flags()
    if isinstance(value, str):
        value = value.strip()
    dec = maxcontext.create_decimal(value)
    if maxcontext.flags[P.Inexact] or \
       maxcontext.flags[P.Rounded] or \
       maxcontext.flags[P.Clamped] or \
       maxcontext.flags[P.InvalidOperation]:
        return context.p._raise_error(P.InvalidOperation)
    if maxcontext.flags[P.FloatOperation]:
        context.p.flags[P.FloatOperation] = True
    return dec






class RestrictedList(list):
    
    def __getattribute__(self, name):
        if name != 'append':
            raise AttributeError("unsupported operation")
        return list.__getattribute__(self, name)
    def unsupported(self, *_):
        raise AttributeError("unsupported operation")
    __add__ = __delattr__ = __delitem__ = __iadd__ = __imul__ = unsupported
    __mul__ = __reversed__ = __rmul__ = __setattr__ = __setitem__ = unsupported

class TestSet(object):
    
    def __init__(self, funcname, operands):
        if funcname.startswith("context."):
            self.funcname = funcname.replace("context.", "")
            self.contextfunc = True
        else:
            self.funcname = funcname
            self.contextfunc = False
        self.op = operands               
        self.context = context           
        self.cop = RestrictedList()      
        self.cex = RestrictedList()      
        self.cresults = RestrictedList() 
        self.pop = RestrictedList()      
        self.pex = RestrictedList()      
        self.presults = RestrictedList() 

        
        
        
        self.with_maxcontext = False
        self.maxcontext = context.c.copy()
        self.maxcontext.prec = C.MAX_PREC
        self.maxcontext.Emax = C.MAX_EMAX
        self.maxcontext.Emin = C.MIN_EMIN
        self.maxcontext.clear_flags()

        self.maxop = RestrictedList()       
        self.maxex = RestrictedList()       
        self.maxresults = RestrictedList()  






class SkipHandler:
    

    def __init__(self):
        self.ulpdiff = 0
        self.powmod_zeros = 0
        self.maxctx = P.Context(Emax=10**18, Emin=-10**18)

    def default(self, t):
        return False
    __ge__ =  __gt__ = __le__ = __lt__ = __ne__ = __eq__ = default
    __reduce__ = __format__ = __repr__ = __str__ = default

    def harrison_ulp(self, dec):
        
        a = dec.next_plus()
        b = dec.next_minus()
        return abs(a - b)

    def standard_ulp(self, dec, prec):
        return _dec_from_triple(0, '1', dec._exp+len(dec._int)-prec)

    def rounding_direction(self, x, mode):
        
        cmp = 1 if x.compare_total(P.Decimal("+0")) >= 0 else -1

        if mode in (P.ROUND_HALF_EVEN, P.ROUND_HALF_UP, P.ROUND_HALF_DOWN):
            return 0
        elif mode == P.ROUND_CEILING:
            return 1
        elif mode == P.ROUND_FLOOR:
            return -1
        elif mode == P.ROUND_UP:
            return cmp
        elif mode == P.ROUND_DOWN:
            return -cmp
        elif mode == P.ROUND_05UP:
            return 2
        else:
            raise ValueError("Unexpected rounding mode: %s" % mode)

    def check_ulpdiff(self, exact, rounded):
        
        p = context.p.prec

        
        x = exact
        if exact.is_infinite():
            x = _dec_from_triple(exact._sign, '10', context.p.Emax)
        y = rounded
        if rounded.is_infinite():
            y = _dec_from_triple(rounded._sign, '10', context.p.Emax)

        
        self.maxctx.prec = p * 2
        t = self.maxctx.subtract(y, x)
        if context.c.flags[C.Clamped] or \
           context.c.flags[C.Underflow]:
            
            ulp = self.harrison_ulp(y)
        else:
            ulp = self.standard_ulp(y, p)
        
        err = self.maxctx.divide(t, ulp)

        dir = self.rounding_direction(x, context.p.rounding)
        if dir == 0:
            if P.Decimal("-0.6") < err < P.Decimal("0.6"):
                return True
        elif dir == 1: 
            if P.Decimal("-0.1") < err < P.Decimal("1.1"):
                return True
        elif dir == -1: 
            if P.Decimal("-1.1") < err < P.Decimal("0.1"):
                return True
        else: 
            if P.Decimal("-1.1") < err < P.Decimal("1.1"):
                return True

        print("ulp: %s  error: %s  exact: %s  c_rounded: %s"
              % (ulp, err, exact, rounded))
        return False

    def bin_resolve_ulp(self, t):
        
        
        if t.rc.is_nan() or t.rp.is_nan():
            return False

        
        self.maxctx.prec = context.p.prec * 2

        op1, op2 = t.pop[0], t.pop[1]
        if t.contextfunc:
            exact = getattr(self.maxctx, t.funcname)(op1, op2)
        else:
            exact = getattr(op1, t.funcname)(op2, context=self.maxctx)

        
        rounded = P.Decimal(t.cresults[0])

        self.ulpdiff += 1
        return self.check_ulpdiff(exact, rounded)

    
    def resolve_underflow(self, t):
        
        if t.cresults != t.presults:
            return False 
        if context.c.flags[C.Rounded] and \
           context.c.flags[C.Inexact] and \
           context.p.flags[P.Rounded] and \
           context.p.flags[P.Inexact]:
            return True 
        return False

    def exp(self, t):
        
        return self.resolve_underflow(t)

    def log10(self, t):
        
        return self.resolve_underflow(t)

    def ln(self, t):
        
        return self.resolve_underflow(t)

    def __pow__(self, t):
        
        if context.c.flags[C.Rounded] and \
           context.c.flags[C.Inexact] and \
           context.p.flags[P.Rounded] and \
           context.p.flags[P.Inexact]:
            return self.bin_resolve_ulp(t)
        else:
            return False
    power = __rpow__ = __pow__

    
    def __float__(self, t):
        
        if t.cop[0].is_nan() and t.pop[0].is_nan():
            return True
        return False
    __complex__ = __float__

    def __radd__(self, t):
        
        if t.rc.is_nan() and t.rp.is_nan():
            return True
        return False
    __rmul__ = __radd__

    
    def __round__(self, t):
        
        if t.rc is None and t.rp.is_nan():
            return True
        return False

shandler = SkipHandler()
def skip_error(t):
    return getattr(shandler, t.funcname, shandler.default)(t)






class VerifyError(Exception):
    
    pass

def function_as_string(t):
    if t.contextfunc:
        cargs = t.cop
        pargs = t.pop
        maxargs = t.maxop
        cfunc = "c_func: %s(" % t.funcname
        pfunc = "p_func: %s(" % t.funcname
        maxfunc = "max_func: %s(" % t.funcname
    else:
        cself, cargs = t.cop[0], t.cop[1:]
        pself, pargs = t.pop[0], t.pop[1:]
        maxself, maxargs = t.maxop[0], t.maxop[1:]
        cfunc = "c_func: %s.%s(" % (repr(cself), t.funcname)
        pfunc = "p_func: %s.%s(" % (repr(pself), t.funcname)
        maxfunc = "max_func: %s.%s(" % (repr(maxself), t.funcname)

    err = cfunc
    for arg in cargs:
        err += "%s, " % repr(arg)
    err = err.rstrip(", ")
    err += ")\n"

    err += pfunc
    for arg in pargs:
        err += "%s, " % repr(arg)
    err = err.rstrip(", ")
    err += ")"

    if t.with_maxcontext:
        err += "\n"
        err += maxfunc
        for arg in maxargs:
            err += "%s, " % repr(arg)
        err = err.rstrip(", ")
        err += ")"

    return err

def raise_error(t):
    global EXIT_STATUS

    if skip_error(t):
        return
    EXIT_STATUS = 1

    err = "Error in %s:\n\n" % t.funcname
    err += "input operands: %s\n\n" % (t.op,)
    err += function_as_string(t)

    err += "\n\nc_result: %s\np_result: %s\n" % (t.cresults, t.presults)
    if t.with_maxcontext:
        err += "max_result: %s\n\n" % (t.maxresults)
    else:
        err += "\n"

    err += "c_exceptions: %s\np_exceptions: %s\n" % (t.cex, t.pex)
    if t.with_maxcontext:
        err += "max_exceptions: %s\n\n" % t.maxex
    else:
        err += "\n"

    err += "%s\n" % str(t.context)
    if t.with_maxcontext:
        err += "%s\n" % str(t.maxcontext)
    else:
        err += "\n"

    raise VerifyError(err)





















def all_nan(a):
    if isinstance(a, C.Decimal):
        return a.is_nan()
    elif isinstance(a, tuple):
        return all(all_nan(v) for v in a)
    return False

def convert(t, convstr=True):
    
    for i, op in enumerate(t.op):

        context.clear_status()
        t.maxcontext.clear_flags()

        if op in RoundModes:
            t.cop.append(op)
            t.pop.append(op)
            t.maxop.append(op)

        elif not t.contextfunc and i == 0 or \
             convstr and isinstance(op, str):
            try:
                c = C.Decimal(op)
                cex = None
            except (TypeError, ValueError, OverflowError) as e:
                c = None
                cex = e.__class__

            try:
                p = RestrictedDecimal(op)
                pex = None
            except (TypeError, ValueError, OverflowError) as e:
                p = None
                pex = e.__class__

            try:
                C.setcontext(t.maxcontext)
                maxop = C.Decimal(op)
                maxex = None
            except (TypeError, ValueError, OverflowError) as e:
                maxop = None
                maxex = e.__class__
            finally:
                C.setcontext(context.c)

            t.cop.append(c)
            t.cex.append(cex)

            t.pop.append(p)
            t.pex.append(pex)

            t.maxop.append(maxop)
            t.maxex.append(maxex)

            if cex is pex:
                if str(c) != str(p) or not context.assert_eq_status():
                    raise_error(t)
                if cex and pex:
                    
                    return 0
            else:
                raise_error(t)

            
            
            if maxex is not None and cex is not maxex:
                raise_error(t)

        elif isinstance(op, Context):
            t.context = op
            t.cop.append(op.c)
            t.pop.append(op.p)
            t.maxop.append(t.maxcontext)

        else:
            t.cop.append(op)
            t.pop.append(op)
            t.maxop.append(op)

    return 1

def callfuncs(t):
    
    context.clear_status()
    t.maxcontext.clear_flags()

    try:
        if t.contextfunc:
            cargs = t.cop
            t.rc = getattr(context.c, t.funcname)(*cargs)
        else:
            cself = t.cop[0]
            cargs = t.cop[1:]
            t.rc = getattr(cself, t.funcname)(*cargs)
        t.cex.append(None)
    except (TypeError, ValueError, OverflowError, MemoryError) as e:
        t.rc = None
        t.cex.append(e.__class__)

    try:
        if t.contextfunc:
            pargs = t.pop
            t.rp = getattr(context.p, t.funcname)(*pargs)
        else:
            pself = t.pop[0]
            pargs = t.pop[1:]
            t.rp = getattr(pself, t.funcname)(*pargs)
        t.pex.append(None)
    except (TypeError, ValueError, OverflowError, MemoryError) as e:
        t.rp = None
        t.pex.append(e.__class__)

    
    
    
    if (t.funcname not in MaxContextSkip and
        not context.c.flags[C.InvalidOperation] and
        not context.c.flags[C.Inexact] and
        not context.c.flags[C.Rounded] and
        not context.c.flags[C.Subnormal] and
        not context.c.flags[C.Clamped] and
        not context.clamp and 
        not any(isinstance(v, C.Context) for v in t.cop)): 
        t.with_maxcontext = True
        try:
            if t.contextfunc:
                maxargs = t.maxop
                t.rmax = getattr(t.maxcontext, t.funcname)(*maxargs)
            else:
                maxself = t.maxop[0]
                maxargs = t.maxop[1:]
                try:
                    C.setcontext(t.maxcontext)
                    t.rmax = getattr(maxself, t.funcname)(*maxargs)
                finally:
                    C.setcontext(context.c)
            t.maxex.append(None)
        except (TypeError, ValueError, OverflowError, MemoryError) as e:
            t.rmax = None
            t.maxex.append(e.__class__)

def verify(t, stat):
    
    t.cresults.append(str(t.rc))
    t.presults.append(str(t.rp))
    if t.with_maxcontext:
        t.maxresults.append(str(t.rmax))

    if isinstance(t.rc, C.Decimal) and isinstance(t.rp, P.Decimal):
        
        t.cresults.append(t.rc.to_eng_string())
        t.cresults.append(t.rc.as_tuple())
        t.cresults.append(str(t.rc.imag))
        t.cresults.append(str(t.rc.real))
        t.presults.append(t.rp.to_eng_string())
        t.presults.append(t.rp.as_tuple())
        t.presults.append(str(t.rp.imag))
        t.presults.append(str(t.rp.real))

        if t.with_maxcontext and isinstance(t.rmax, C.Decimal):
            t.maxresults.append(t.rmax.to_eng_string())
            t.maxresults.append(t.rmax.as_tuple())
            t.maxresults.append(str(t.rmax.imag))
            t.maxresults.append(str(t.rmax.real))

        nc = t.rc.number_class().lstrip('+-s')
        stat[nc] += 1
    else:
        
        if not isinstance(t.rc, tuple) and not isinstance(t.rp, tuple):
            if t.rc != t.rp:
                raise_error(t)
            if t.with_maxcontext and not isinstance(t.rmax, tuple):
                if t.rmax != t.rc:
                    raise_error(t)
        stat[type(t.rc).__name__] += 1

    
    if t.cresults != t.presults:
        raise_error(t)
    
    if t.cex != t.pex:
        raise_error(t)
    
    if not t.context.assert_eq_status():
        raise_error(t)

    if t.with_maxcontext:
        
        if all_nan(t.rc) and all_nan(t.rmax):
            return
        
        if t.maxresults != t.cresults:
            raise_error(t)
        
        if t.maxex != t.cex:
            raise_error(t)
        
        if t.maxcontext.flags != t.context.c.flags:
            raise_error(t)





























def log(fmt, args=None):
    if args:
        sys.stdout.write(''.join((fmt, '\n')) % args)
    else:
        sys.stdout.write(''.join((str(fmt), '\n')))
    sys.stdout.flush()

def test_method(method, testspecs, testfunc):
    
    log("testing %s ...", method)
    stat = defaultdict(int)
    for spec in testspecs:
        if 'samples' in spec:
            spec['prec'] = sorted(random.sample(range(1, 101),
                                  spec['samples']))
        for prec in spec['prec']:
            context.prec = prec
            for expts in spec['expts']:
                emin, emax = expts
                if emin == 'rand':
                    context.Emin = random.randrange(-1000, 0)
                    context.Emax = random.randrange(prec, 1000)
                else:
                    context.Emin, context.Emax = emin, emax
                if prec > context.Emax: continue
                log("    prec: %d  emin: %d  emax: %d",
                    (context.prec, context.Emin, context.Emax))
                restr_range = 9999 if context.Emax > 9999 else context.Emax+99
                for rounding in RoundModes:
                    context.rounding = rounding
                    context.capitals = random.randrange(2)
                    if spec['clamp'] == 'rand':
                        context.clamp = random.randrange(2)
                    else:
                        context.clamp = spec['clamp']
                    exprange = context.c.Emax
                    testfunc(method, prec, exprange, restr_range,
                             spec['iter'], stat)
    log("    result types: %s" % sorted([t for t in stat.items()]))

def test_unary(method, prec, exp_range, restricted_range, itr, stat):
    
    if method in UnaryRestricted:
        exp_range = restricted_range
    for op in all_unary(prec, exp_range, itr):
        t = TestSet(method, op)
        try:
            if not convert(t):
                continue
            callfuncs(t)
            verify(t, stat)
        except VerifyError as err:
            log(err)

    if not method.startswith('__'):
        for op in unary_optarg(prec, exp_range, itr):
            t = TestSet(method, op)
            try:
                if not convert(t):
                    continue
                callfuncs(t)
                verify(t, stat)
            except VerifyError as err:
                log(err)

def test_binary(method, prec, exp_range, restricted_range, itr, stat):
    
    if method in BinaryRestricted:
        exp_range = restricted_range
    for op in all_binary(prec, exp_range, itr):
        t = TestSet(method, op)
        try:
            if not convert(t):
                continue
            callfuncs(t)
            verify(t, stat)
        except VerifyError as err:
            log(err)

    if not method.startswith('__'):
        for op in binary_optarg(prec, exp_range, itr):
            t = TestSet(method, op)
            try:
                if not convert(t):
                    continue
                callfuncs(t)
                verify(t, stat)
            except VerifyError as err:
                log(err)

def test_ternary(method, prec, exp_range, restricted_range, itr, stat):
    
    if method in TernaryRestricted:
        exp_range = restricted_range
    for op in all_ternary(prec, exp_range, itr):
        t = TestSet(method, op)
        try:
            if not convert(t):
                continue
            callfuncs(t)
            verify(t, stat)
        except VerifyError as err:
            log(err)

    if not method.startswith('__'):
        for op in ternary_optarg(prec, exp_range, itr):
            t = TestSet(method, op)
            try:
                if not convert(t):
                    continue
                callfuncs(t)
                verify(t, stat)
            except VerifyError as err:
                log(err)

def test_format(method, prec, exp_range, restricted_range, itr, stat):
    
    for op in all_unary(prec, exp_range, itr):
        fmt1 = rand_format(chr(random.randrange(0, 128)), 'EeGgn')
        fmt2 = rand_locale()
        for fmt in (fmt1, fmt2):
            fmtop = (op[0], fmt)
            t = TestSet(method, fmtop)
            try:
                if not convert(t, convstr=False):
                    continue
                callfuncs(t)
                verify(t, stat)
            except VerifyError as err:
                log(err)
    for op in all_unary(prec, 9999, itr):
        fmt1 = rand_format(chr(random.randrange(0, 128)), 'Ff%')
        fmt2 = rand_locale()
        for fmt in (fmt1, fmt2):
            fmtop = (op[0], fmt)
            t = TestSet(method, fmtop)
            try:
                if not convert(t, convstr=False):
                    continue
                callfuncs(t)
                verify(t, stat)
            except VerifyError as err:
                log(err)

def test_round(method, prec, exprange, restricted_range, itr, stat):
    
    for op in all_unary(prec, 9999, itr):
        n = random.randrange(10)
        roundop = (op[0], n)
        t = TestSet(method, roundop)
        try:
            if not convert(t):
                continue
            callfuncs(t)
            verify(t, stat)
        except VerifyError as err:
            log(err)

def test_from_float(method, prec, exprange, restricted_range, itr, stat):
    
    for rounding in RoundModes:
        context.rounding = rounding
        for i in range(1000):
            f = randfloat()
            op = (f,) if method.startswith("context.") else ("sNaN", f)
            t = TestSet(method, op)
            try:
                if not convert(t):
                    continue
                callfuncs(t)
                verify(t, stat)
            except VerifyError as err:
                log(err)

def randcontext(exprange):
    c = Context(C.Context(), P.Context())
    c.Emax = random.randrange(1, exprange+1)
    c.Emin = random.randrange(-exprange, 0)
    maxprec = 100 if c.Emax >= 100 else c.Emax
    c.prec = random.randrange(1, maxprec+1)
    c.clamp = random.randrange(2)
    c.clear_traps()
    return c

def test_quantize_api(method, prec, exprange, restricted_range, itr, stat):
    
    for op in all_binary(prec, restricted_range, itr):
        for rounding in RoundModes:
            c = randcontext(exprange)
            quantizeop = (op[0], op[1], rounding, c)
            t = TestSet(method, quantizeop)
            try:
                if not convert(t):
                    continue
                callfuncs(t)
                verify(t, stat)
            except VerifyError as err:
                log(err)


def check_untested(funcdict, c_cls, p_cls):
    
    c_attr = set(dir(c_cls))
    p_attr = set(dir(p_cls))
    intersect = c_attr & p_attr

    funcdict['c_only'] = tuple(sorted(c_attr-intersect))
    funcdict['p_only'] = tuple(sorted(p_attr-intersect))

    tested = set()
    for lst in funcdict.values():
        for v in lst:
            v = v.replace("context.", "") if c_cls == C.Context else v
            tested.add(v)

    funcdict['untested'] = tuple(sorted(intersect-tested))

    
    
    


if __name__ == '__main__':

    parser = argparse.ArgumentParser(prog="deccheck.py")

    group = parser.add_mutually_exclusive_group()
    group.add_argument('--short', dest='time', action="store_const", const='short', default='short', help="short test (default)")
    group.add_argument('--medium', dest='time', action="store_const", const='medium', default='short', help="medium test (reasonable run time)")
    group.add_argument('--long', dest='time', action="store_const", const='long', default='short', help="long test (long run time)")
    group.add_argument('--all', dest='time', action="store_const", const='all', default='short', help="all tests (excessive run time)")

    group = parser.add_mutually_exclusive_group()
    group.add_argument('--single', dest='single', nargs=1, default=False, metavar="TEST", help="run a single test")
    group.add_argument('--multicore', dest='multicore', action="store_true", default=False, help="use all available cores")

    args = parser.parse_args()
    assert args.single is False or args.multicore is False
    if args.single:
        args.single = args.single[0]


    
    
    
    base_expts = [(C.MIN_EMIN, C.MAX_EMAX)]
    if C.MAX_EMAX == 999999999999999999:
        base_expts.append((-999999999, 999999999))

    
    base = {
        'expts': base_expts,
        'prec': [],
        'clamp': 'rand',
        'iter': None,
        'samples': None,
    }
    
    small = {
        'prec': [1, 2, 3, 4, 5],
        'expts': [(-1, 1), (-2, 2), (-3, 3), (-4, 4), (-5, 5)],
        'clamp': 'rand',
        'iter': None
    }
    
    ieee = [
        
        {'prec': [7], 'expts': [(-95, 96)], 'clamp': 1, 'iter': None},
        
        {'prec': [16], 'expts': [(-383, 384)], 'clamp': 1, 'iter': None},
        
        {'prec': [34], 'expts': [(-6143, 6144)], 'clamp': 1, 'iter': None}
    ]

    if args.time == 'medium':
        base['expts'].append(('rand', 'rand'))
        
        base['samples'] = 5
        testspecs = [small] + ieee + [base]
    elif args.time == 'long':
        base['expts'].append(('rand', 'rand'))
        
        base['samples'] = 10
        testspecs = [small] + ieee + [base]
    elif args.time == 'all':
        base['expts'].append(('rand', 'rand'))
        
        base['samples'] = 100
        testspecs = [small] + ieee + [base]
    else: 
        rand_ieee = random.choice(ieee)
        base['iter'] = small['iter'] = rand_ieee['iter'] = 1
        
        base['samples'] = 1
        base['expts'] = [random.choice(base_expts)]
        
        prec = random.randrange(1, 6)
        small['prec'] = [prec]
        small['expts'] = [(-prec, prec)]
        testspecs = [small, rand_ieee, base]


    check_untested(Functions, C.Decimal, P.Decimal)
    check_untested(ContextFunctions, C.Context, P.Context)


    if args.multicore:
        q = Queue()
    elif args.single:
        log("Random seed: %d", RANDSEED)
    else:
        log("\n\nRandom seed: %d\n\n", RANDSEED)


    FOUND_METHOD = False
    def do_single(method, f):
        global FOUND_METHOD
        if args.multicore:
            q.put(method)
        elif not args.single or args.single == method:
            FOUND_METHOD = True
            f()

    
    for method in Functions['unary'] + Functions['unary_ctx'] + \
                  Functions['unary_rnd_ctx']:
        do_single(method, lambda: test_method(method, testspecs, test_unary))

    for method in Functions['binary'] + Functions['binary_ctx']:
        do_single(method, lambda: test_method(method, testspecs, test_binary))

    for method in Functions['ternary'] + Functions['ternary_ctx']:
        name = '__powmod__' if method == '__pow__' else method
        do_single(name, lambda: test_method(method, testspecs, test_ternary))

    do_single('__format__', lambda: test_method('__format__', testspecs, test_format))
    do_single('__round__', lambda: test_method('__round__', testspecs, test_round))
    do_single('from_float', lambda: test_method('from_float', testspecs, test_from_float))
    do_single('quantize_api', lambda: test_method('quantize', testspecs, test_quantize_api))

    
    for method in ContextFunctions['unary']:
        do_single(method, lambda: test_method(method, testspecs, test_unary))

    for method in ContextFunctions['binary']:
        do_single(method, lambda: test_method(method, testspecs, test_binary))

    for method in ContextFunctions['ternary']:
        name = 'context.powmod' if method == 'context.power' else method
        do_single(name, lambda: test_method(method, testspecs, test_ternary))

    do_single('context.create_decimal_from_float',
              lambda: test_method('context.create_decimal_from_float',
                                   testspecs, test_from_float))

    if args.multicore:
        error = Event()
        write_lock = Lock()

        def write_output(out, returncode):
            if returncode != 0:
                error.set()

            with write_lock:
                sys.stdout.buffer.write(out + b"\n")
                sys.stdout.buffer.flush()

        def tfunc():
            while not error.is_set():
                try:
                    test = q.get(block=False, timeout=-1)
                except Empty:
                    return

                cmd = [sys.executable, "deccheck.py", "--%s" % args.time, "--single", test]
                p = subprocess.Popen(cmd, stdout=PIPE, stderr=STDOUT)
                out, _ = p.communicate()
                write_output(out, p.returncode)

        N = os.process_cpu_count()
        t = N * [None]

        for i in range(N):
            t[i] = Thread(target=tfunc)
            t[i].start()

        for i in range(N):
            t[i].join()

        sys.exit(1 if error.is_set() else 0)

    elif args.single:
        if not FOUND_METHOD:
            log("\nerror: cannot find method \"%s\"" % args.single)
            EXIT_STATUS = 1
        sys.exit(EXIT_STATUS)
    else:
        sys.exit(EXIT_STATUS)