mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1397 lines
		
	
	
	
		
			48 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1397 lines
		
	
	
	
		
			48 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #
 | |
| # Copyright (c) 2008-2012 Stefan Krah. All rights reserved.
 | |
| #
 | |
| # 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 copyright
 | |
| #    notice, this list of conditions and the following disclaimer.
 | |
| #
 | |
| # 2. Redistributions in binary form must reproduce the above copyright
 | |
| #    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 AUTHOR 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 AUTHOR 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]
 | |
| #
 | |
| 
 | |
| 
 | |
| 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 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
 | |
| 
 | |
| from _testcapi import decimal_as_triple
 | |
| from _testcapi import decimal_from_triple
 | |
| 
 | |
| import _decimal as C
 | |
| import _pydecimal as P
 | |
| 
 | |
| 
 | |
| EXIT_STATUS = 0
 | |
| 
 | |
| 
 | |
| # Contains all categories of Decimal methods.
 | |
| Functions = {
 | |
|     # Plain unary:
 | |
|     '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 with optional context:
 | |
|     'unary_ctx': (
 | |
|         'exp', 'is_normal', 'is_subnormal', 'ln', 'log10', 'logb',
 | |
|         'logical_invert', 'next_minus', 'next_plus', 'normalize',
 | |
|         'number_class', 'sqrt', 'to_eng_string'
 | |
|     ),
 | |
|     # Unary with optional rounding mode and context:
 | |
|     'unary_rnd_ctx': ('to_integral', 'to_integral_exact', 'to_integral_value'),
 | |
|     # Plain binary:
 | |
|     '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 with optional context:
 | |
|     'binary_ctx': (
 | |
|         'compare', 'compare_signal', 'logical_and', 'logical_or', 'logical_xor',
 | |
|         'max', 'max_mag', 'min', 'min_mag', 'next_toward', 'remainder_near',
 | |
|         'rotate', 'scaleb', 'shift'
 | |
|     ),
 | |
|     # Plain ternary:
 | |
|     'ternary': ('__pow__',),
 | |
|     # Ternary with optional context:
 | |
|     'ternary_ctx': ('fma',),
 | |
|     # Special:
 | |
|     'special': ('__format__', '__reduce_ex__', '__round__', 'from_float',
 | |
|                 'quantize'),
 | |
|     # Properties:
 | |
|     'property': ('real', 'imag')
 | |
| }
 | |
| 
 | |
| # Contains all categories of Context methods. The n-ary classification
 | |
| # applies to the number of Decimal arguments.
 | |
| ContextFunctions = {
 | |
|     # Plain nullary:
 | |
|     'nullary': ('context.__hash__', 'context.__reduce__', 'context.radix'),
 | |
|     # Plain unary:
 | |
|     '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'
 | |
|     ),
 | |
|     # Plain binary:
 | |
|     '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'
 | |
|     ),
 | |
|     # Plain ternary:
 | |
|     'ternary': ('context.fma', 'context.power'),
 | |
|     # Special:
 | |
|     'special': ('context.__reduce_ex__', 'context.create_decimal_from_float')
 | |
| }
 | |
| 
 | |
| # Functions that set no context flags but whose result can differ depending
 | |
| # on prec, Emin and Emax.
 | |
| MaxContextSkip = ['is_normal', 'is_subnormal', 'logical_invert', 'next_minus',
 | |
|                   'next_plus', 'number_class', 'logical_and', 'logical_or',
 | |
|                   'logical_xor', 'next_toward', 'rotate', 'shift']
 | |
| 
 | |
| # Functions that require a restricted exponent range for reasonable runtimes.
 | |
| UnaryRestricted = [
 | |
|   '__ceil__', '__floor__', '__int__', '__trunc__',
 | |
|   'as_integer_ratio', 'to_integral', 'to_integral_value'
 | |
| ]
 | |
| 
 | |
| BinaryRestricted = ['__round__']
 | |
| 
 | |
| TernaryRestricted = ['__pow__', 'context.power']
 | |
| 
 | |
| 
 | |
| # ======================================================================
 | |
| #                            Triple tests
 | |
| # ======================================================================
 | |
| 
 | |
| def c_as_triple(dec):
 | |
|     sign, hi, lo, exp = decimal_as_triple(dec)
 | |
| 
 | |
|     coeff = hi * 2**64 + lo
 | |
|     return (sign, coeff, exp)
 | |
| 
 | |
| def c_from_triple(triple):
 | |
|     sign, coeff, exp = triple
 | |
| 
 | |
|     hi = coeff // 2**64
 | |
|     lo = coeff % 2**64
 | |
|     return decimal_from_triple((sign, hi, lo, exp))
 | |
| 
 | |
| def p_as_triple(dec):
 | |
|     sign, digits, exp = dec.as_tuple()
 | |
| 
 | |
|     s = "".join(str(d) for d in digits)
 | |
|     coeff = int(s) if s else 0
 | |
| 
 | |
|     if coeff < 0 or coeff >= 2**128:
 | |
|         raise ValueError("value out of bounds for a uint128 triple")
 | |
| 
 | |
|     return (sign, coeff, exp)
 | |
| 
 | |
| def p_from_triple(triple):
 | |
|     sign, coeff, exp = triple
 | |
| 
 | |
|     if coeff < 0 or coeff >= 2**128:
 | |
|         raise ValueError("value out of bounds for a uint128 triple")
 | |
| 
 | |
|     digits = tuple(int(c) for c in str(coeff))
 | |
| 
 | |
|     return P.Decimal((sign, digits, exp))
 | |
| 
 | |
| 
 | |
| # ======================================================================
 | |
| #                            Unified Context
 | |
| # ======================================================================
 | |
| 
 | |
| # Translate symbols.
 | |
| 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):
 | |
|     """Provides a convenient way of syncing the C and P contexts"""
 | |
| 
 | |
|     __slots__ = ['c', 'p']
 | |
| 
 | |
|     def __init__(self, c_ctx=None, p_ctx=None):
 | |
|         """Initialization is from the C context"""
 | |
|         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):
 | |
|         """lst: C signal list"""
 | |
|         self.clear_traps()
 | |
|         for signal in lst:
 | |
|             self.c.traps[signal] = True
 | |
|             self.p.traps[CondMap[signal]] = True
 | |
| 
 | |
|     def setstatus(self, lst):
 | |
|         """lst: C signal list"""
 | |
|         self.clear_status()
 | |
|         for signal in lst:
 | |
|             self.c.flags[signal] = True
 | |
|             self.p.flags[CondMap[signal]] = True
 | |
| 
 | |
|     def assert_eq_status(self):
 | |
|         """assert equality of C and P status"""
 | |
|         for signal in self.c.flags:
 | |
|             if self.c.flags[signal] == (not self.p.flags[CondMap[signal]]):
 | |
|                 return False
 | |
|         return True
 | |
| 
 | |
| 
 | |
| # We don't want exceptions so that we can compare the status flags.
 | |
| context = Context()
 | |
| context.Emin = C.MIN_EMIN
 | |
| context.Emax = C.MAX_EMAX
 | |
| context.clear_traps()
 | |
| 
 | |
| # When creating decimals, _decimal is ultimately limited by the maximum
 | |
| # context values. We emulate this restriction for decimal.py.
 | |
| 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
 | |
| 
 | |
| 
 | |
| # ======================================================================
 | |
| #      TestSet: Organize data and events during a single test case
 | |
| # ======================================================================
 | |
| 
 | |
| class RestrictedList(list):
 | |
|     """List that can only be modified by appending items."""
 | |
|     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):
 | |
|     """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.
 | |
|     """
 | |
|     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               # raw operand tuple
 | |
|         self.context = context           # context used for the operation
 | |
|         self.cop = RestrictedList()      # converted C.Decimal operands
 | |
|         self.cex = RestrictedList()      # Python exceptions for C.Decimal
 | |
|         self.cresults = RestrictedList() # C.Decimal results
 | |
|         self.pop = RestrictedList()      # converted P.Decimal operands
 | |
|         self.pex = RestrictedList()      # Python exceptions for P.Decimal
 | |
|         self.presults = RestrictedList() # 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.
 | |
|         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()       # converted C.Decimal operands
 | |
|         self.maxex = RestrictedList()       # Python exceptions for C.Decimal
 | |
|         self.maxresults = RestrictedList()  # C.Decimal results
 | |
| 
 | |
| 
 | |
| # ======================================================================
 | |
| #                SkipHandler: skip known discrepancies
 | |
| # ======================================================================
 | |
| 
 | |
| class SkipHandler:
 | |
|     """Handle known discrepancies between decimal.py and _decimal.so.
 | |
|        These are either ULP differences in the power function or
 | |
|        extremely minor issues."""
 | |
| 
 | |
|     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):
 | |
|         """ftp://ftp.inria.fr/INRIA/publication/publi-pdf/RR/RR-5504.pdf"""
 | |
|         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):
 | |
|         """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."""
 | |
|         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):
 | |
|         # current precision
 | |
|         p = context.p.prec
 | |
| 
 | |
|         # Convert infinities to the largest representable number + 1.
 | |
|         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)
 | |
| 
 | |
|         # err = (rounded - exact) / ulp(rounded)
 | |
|         self.maxctx.prec = p * 2
 | |
|         t = self.maxctx.subtract(y, x)
 | |
|         if context.c.flags[C.Clamped] or \
 | |
|            context.c.flags[C.Underflow]:
 | |
|             # The standard ulp does not work in Underflow territory.
 | |
|             ulp = self.harrison_ulp(y)
 | |
|         else:
 | |
|             ulp = self.standard_ulp(y, p)
 | |
|         # Error in ulps.
 | |
|         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: # directed, upwards
 | |
|             if P.Decimal("-0.1") < err < P.Decimal("1.1"):
 | |
|                 return True
 | |
|         elif dir == -1: # directed, downwards
 | |
|             if P.Decimal("-1.1") < err < P.Decimal("0.1"):
 | |
|                 return True
 | |
|         else: # ROUND_05UP
 | |
|             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):
 | |
|         """Check if results of _decimal's power function are within the
 | |
|            allowed ulp ranges."""
 | |
|         # NaNs are beyond repair.
 | |
|         if t.rc.is_nan() or t.rp.is_nan():
 | |
|             return False
 | |
| 
 | |
|         # "exact" result, double precision, half_even
 | |
|         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)
 | |
| 
 | |
|         # _decimal's rounded result
 | |
|         rounded = P.Decimal(t.cresults[0])
 | |
| 
 | |
|         self.ulpdiff += 1
 | |
|         return self.check_ulpdiff(exact, rounded)
 | |
| 
 | |
|     ############################ Correct rounding #############################
 | |
|     def resolve_underflow(self, t):
 | |
|         """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()
 | |
|         """
 | |
|         if t.cresults != t.presults:
 | |
|             return False # Results must be identical.
 | |
|         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 # Subnormal/Underflow may be missing.
 | |
|         return False
 | |
| 
 | |
|     def exp(self, t):
 | |
|         """Resolve Underflow or ULP difference."""
 | |
|         return self.resolve_underflow(t)
 | |
| 
 | |
|     def log10(self, t):
 | |
|         """Resolve Underflow or ULP difference."""
 | |
|         return self.resolve_underflow(t)
 | |
| 
 | |
|     def ln(self, t):
 | |
|         """Resolve Underflow or ULP difference."""
 | |
|         return self.resolve_underflow(t)
 | |
| 
 | |
|     def __pow__(self, t):
 | |
|         """Always calls the resolve function. C.Decimal does not have correct
 | |
|            rounding for the power function."""
 | |
|         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__
 | |
| 
 | |
|     ############################## Technicalities #############################
 | |
|     def __float__(self, t):
 | |
|         """NaN comparison in the verify() function obviously gives an
 | |
|            incorrect answer:  nan == nan -> False"""
 | |
|         if t.cop[0].is_nan() and t.pop[0].is_nan():
 | |
|             return True
 | |
|         return False
 | |
|     __complex__ = __float__
 | |
| 
 | |
|     def __radd__(self, t):
 | |
|         """decimal.py gives precedence to the first NaN; this is
 | |
|            not important, as __radd__ will not be called for
 | |
|            two decimal arguments."""
 | |
|         if t.rc.is_nan() and t.rp.is_nan():
 | |
|             return True
 | |
|         return False
 | |
|     __rmul__ = __radd__
 | |
| 
 | |
|     ################################ Various ##################################
 | |
|     def __round__(self, t):
 | |
|         """Exception: Decimal('1').__round__(-100000000000000000000000000)
 | |
|            Should it really be InvalidOperation?"""
 | |
|         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)
 | |
| 
 | |
| 
 | |
| # ======================================================================
 | |
| #                      Handling verification errors
 | |
| # ======================================================================
 | |
| 
 | |
| class VerifyError(Exception):
 | |
|     """Verification failed."""
 | |
|     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)
 | |
| 
 | |
| 
 | |
| # ======================================================================
 | |
| #                        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.
 | |
| # ======================================================================
 | |
| 
 | |
| 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):
 | |
|     """ 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.
 | |
|     """
 | |
|     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:
 | |
|                     # nothing to test
 | |
|                     return 0
 | |
|             else:
 | |
|                 raise_error(t)
 | |
| 
 | |
|             # The exceptions in the maxcontext operation can legitimately
 | |
|             # differ, only test that maxex implies cex:
 | |
|             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):
 | |
|     """ 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.
 | |
|     """
 | |
|     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 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.
 | |
|     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 # results are padded to context.prec if context.clamp==1.
 | |
|         not any(isinstance(v, C.Context) for v in t.cop)): # another context is used.
 | |
|         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 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.
 | |
|     """
 | |
|     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):
 | |
|         # General case: both results are Decimals.
 | |
|         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))
 | |
| 
 | |
|         ctriple = None
 | |
|         if str(t.rc) == str(t.rp): # see skip handler
 | |
|             try:
 | |
|                 ctriple = c_as_triple(t.rc)
 | |
|             except ValueError:
 | |
|                 try:
 | |
|                     ptriple = p_as_triple(t.rp)
 | |
|                 except ValueError:
 | |
|                     pass
 | |
|                 else:
 | |
|                     raise RuntimeError("ValueError not raised")
 | |
|             else:
 | |
|                 cres = c_from_triple(ctriple)
 | |
|                 t.cresults.append(ctriple)
 | |
|                 t.cresults.append(str(cres))
 | |
| 
 | |
|                 ptriple = p_as_triple(t.rp)
 | |
|                 pres = p_from_triple(ptriple)
 | |
|                 t.presults.append(ptriple)
 | |
|                 t.presults.append(str(pres))
 | |
| 
 | |
|         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))
 | |
| 
 | |
|             if ctriple is not None:
 | |
|                 # NaN payloads etc. depend on precision and clamp.
 | |
|                 if all_nan(t.rc) and all_nan(t.rmax):
 | |
|                     t.maxresults.append(ctriple)
 | |
|                     t.maxresults.append(str(cres))
 | |
|                 else:
 | |
|                     maxtriple = c_as_triple(t.rmax)
 | |
|                     maxres = c_from_triple(maxtriple)
 | |
|                     t.maxresults.append(maxtriple)
 | |
|                     t.maxresults.append(str(maxres))
 | |
| 
 | |
|         nc = t.rc.number_class().lstrip('+-s')
 | |
|         stat[nc] += 1
 | |
|     else:
 | |
|         # Results from e.g. __divmod__ can only be compared as strings.
 | |
|         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
 | |
| 
 | |
|     # The return value lists must be equal.
 | |
|     if t.cresults != t.presults:
 | |
|         raise_error(t)
 | |
|     # The Python exception lists (TypeError, etc.) must be equal.
 | |
|     if t.cex != t.pex:
 | |
|         raise_error(t)
 | |
|     # The context flags must be equal.
 | |
|     if not t.context.assert_eq_status():
 | |
|         raise_error(t)
 | |
| 
 | |
|     if t.with_maxcontext:
 | |
|         # NaN payloads etc. depend on precision and clamp.
 | |
|         if all_nan(t.rc) and all_nan(t.rmax):
 | |
|             return
 | |
|         # The return value lists must be equal.
 | |
|         if t.maxresults != t.cresults:
 | |
|             raise_error(t)
 | |
|         # The Python exception lists (TypeError, etc.) must be equal.
 | |
|         if t.maxex != t.cex:
 | |
|             raise_error(t)
 | |
|         # The context flags must be equal.
 | |
|         if t.maxcontext.flags != t.context.c.flags:
 | |
|             raise_error(t)
 | |
| 
 | |
| 
 | |
| # ======================================================================
 | |
| #                           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.
 | |
| # ======================================================================
 | |
| 
 | |
| 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):
 | |
|     """Iterate a test function through many context settings."""
 | |
|     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):
 | |
|     """Iterate a unary function through many test cases."""
 | |
|     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):
 | |
|     """Iterate a binary function through many test cases."""
 | |
|     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):
 | |
|     """Iterate a ternary function through many test cases."""
 | |
|     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):
 | |
|     """Iterate the __format__ method through many test cases."""
 | |
|     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):
 | |
|     """Iterate the __round__ method through many test cases."""
 | |
|     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):
 | |
|     """Iterate the __float__ method through many test cases."""
 | |
|     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):
 | |
|     """Iterate the 'quantize' method through many test cases, using
 | |
|        the optional arguments."""
 | |
|     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):
 | |
|     """Determine untested, C-only and Python-only attributes.
 | |
|        Uncomment print lines for debugging."""
 | |
|     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))
 | |
| 
 | |
|     # 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]))
 | |
| 
 | |
| 
 | |
| 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]
 | |
| 
 | |
| 
 | |
|     # Set up the testspecs list. A testspec is simply a dictionary
 | |
|     # that determines the amount of different contexts that 'test_method'
 | |
|     # will generate.
 | |
|     base_expts = [(C.MIN_EMIN, C.MAX_EMAX)]
 | |
|     if C.MAX_EMAX == 999999999999999999:
 | |
|         base_expts.append((-999999999, 999999999))
 | |
| 
 | |
|     # Basic contexts.
 | |
|     base = {
 | |
|         'expts': base_expts,
 | |
|         '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
 | |
|         rand_ieee = random.choice(ieee)
 | |
|         base['iter'] = small['iter'] = rand_ieee['iter'] = 1
 | |
|         # 1 random precision and exponent pair
 | |
|         base['samples'] = 1
 | |
|         base['expts'] = [random.choice(base_expts)]
 | |
|         # 1 random precision and exponent pair
 | |
|         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()
 | |
| 
 | |
|     # Decimal methods:
 | |
|     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))
 | |
| 
 | |
|     # Context methods:
 | |
|     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.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)
 | 
