#!/usr/bin/env python # # Copyright (C) 2008, 2009 Stefan Krah # # This file is part of libmpdec. # # libmpdec is free software: you can redistribute it and/or modify it # under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # libmpdec is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty # of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See # the GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with libmpdec. If not, see . # # # The cdec (for "check decimal") class checks fastdec.so against decimal.py. # A cdec object consists of an mpd and a Decimal. Every operation is carried # out on both types. If the results don't match, an exception is raised. # import fastdec import decimal import sys # Python 2.5 can require exorbitant amounts of memory for hashing integers. PY25_HASH_HAVE_WARNED = 0 # No __trunc__, ... in 2.5 py_minor = sys.version_info[1] # Translate fastdec symbols to decimal.py symbols. decflags = { fastdec.Clamped: decimal.Clamped, # fastdec.ConversionSyntax: decimal.ConversionSyntax, fastdec.DivisionByZero: decimal.DivisionByZero, # fastdec.DivisionImpossible: decimal.DivisionImpossible, # fastdec.DivisionUndefined: decimal.DivisionUndefined, fastdec.Inexact: decimal.Inexact, # fastdec.InvalidContext: decimal.InvalidContext, fastdec.InvalidOperation: decimal.InvalidOperation, fastdec.Overflow: decimal.Overflow, fastdec.Rounded: decimal.Rounded, fastdec.Subnormal: decimal.Subnormal, fastdec.Underflow: decimal.Underflow, } decround = { fastdec.ROUND_UP: decimal.ROUND_UP, fastdec.ROUND_DOWN: decimal.ROUND_DOWN, fastdec.ROUND_CEILING: decimal.ROUND_CEILING, fastdec.ROUND_FLOOR: decimal.ROUND_FLOOR, fastdec.ROUND_HALF_UP: decimal.ROUND_HALF_UP, fastdec.ROUND_HALF_DOWN: decimal.ROUND_HALF_DOWN, fastdec.ROUND_HALF_EVEN: decimal.ROUND_HALF_EVEN, fastdec.ROUND_05UP: decimal.ROUND_05UP } # Methods that are not in both modules (yet) fddiff = {# decimal # fastdec # '__complex__': NotImplemented, # 'capitals': NotImplemented, # 'clear_flags': NotImplemented, # 'copy_decimal': NotImplemented, # 'imag': NotImplemented, # NotImplemented: 'invroot', # NotImplemented: 'powmod', # 'real': NotImplemented, # NotImplemented 'reduce', same as normalize # NotImplemented: 'sign', } # All common methods fdall = [ '__abs__', '__add__', '__class__', '__copy__', '__deepcopy__', '__delattr__', '__div__', '__divmod__', '__doc__', '__eq__', '__float__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__int__', '__le__', '__long__', '__lt__', '__mod__', '__module__', '__mul__', '__ne__', '__neg__', '__new__', '__nonzero__', '__pos__', '__pow__', '__radd__', '__rdiv__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rmod__', '__rmul__', '__rpow__', '__rsub__', '__rtruediv__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', 'abs', 'add', 'canonical', 'compare', 'compare_signal', 'compare_total', 'compare_total_mag', 'copy_abs', 'copy_negate', 'copy_sign', 'divide', 'divide_int', 'divmod', 'exp', 'fma', 'is_canonical', 'is_finite', 'is_infinite', 'is_nan', 'is_normal', 'is_qnan', 'is_signed', 'is_snan', 'is_subnormal', 'is_zero', 'ln', 'log10', 'logb', 'logical_and', 'logical_invert', 'logical_or', 'logical_xor', 'max', 'max_mag', 'min', 'min_mag', 'minus', 'multiply', 'next_minus', 'next_plus', 'next_toward', 'normalize', 'number_class', 'plus', 'power', 'quantize', 'radix', 'remainder', 'remainder_near', 'rotate', 'same_quantum', 'scaleb', 'shift', 'sqrt', 'subtract', 'to_eng_string', 'to_integral', 'to_integral_exact', 'to_integral_value', 'to_sci_string' ] # Unary methods fdunary = [ '__abs__', '__copy__', '__deepcopy__', '__float__', '__hash__', '__int__', '__long__', '__neg__', '__nonzero__', '__pos__', '__repr__', '__str__', 'abs', 'canonical', 'copy_abs', 'copy_negate', 'exp', 'is_canonical', 'is_finite', 'is_infinite', 'is_nan', 'is_normal', 'is_qnan', 'is_signed', 'is_snan', 'is_subnormal', 'is_zero', 'ln', 'log10', 'logb', 'logical_invert', 'minus', 'next_minus', 'next_plus', 'normalize', 'number_class', 'plus', 'radix', 'sqrt', 'to_eng_string', 'to_integral', 'to_integral_exact', 'to_integral_value', 'to_sci_string' ] # Binary methods fdbinary = [ '__add__', '__div__', '__divmod__', '__eq__', '__floordiv__', '__mod__', '__mul__', '__ne__', '__pow__', '__sub__', '__truediv__', 'add', 'copy_sign', 'compare', 'compare_signal', 'compare_total', 'compare_total_mag', 'divide', 'divide_int', 'divmod', 'logical_and', 'logical_or', 'logical_xor', 'max', 'max_mag', 'min', 'min_mag', 'multiply', 'next_toward', 'power', 'quantize', 'remainder', 'remainder_near', 'rotate', 'same_quantum', 'scaleb', 'shift', 'subtract' ] # Ternary methods fdternary = ['__pow__', 'fma', 'power'] if py_minor >= 6: fdunary.append('__trunc__') fdbinary.extend(['__ge__', '__gt__', '__le__', '__lt__']) class Context(object): __slots__ = ['f', 'd'] def __init__(self, fdctx): self.f = fastdec.getcontext() self.d = decimal.getcontext() self.setprec(fdctx.prec) self.setemin(fdctx.emin) self.setemax(fdctx.emax) self.setround(fdctx.round) self.settraps(fdctx.gettraps()) self.setstatus(fdctx.getstatus()) def getprec(self): return self.f.prec def setprec(self, val): self.f.prec = val self.d.prec = val def getemin(self): return self.f.emin def setemin(self, val): self.f.emin = val self.d.Emin = val def getemax(self): return self.f.emax def setemax(self, val): self.f.emax = val self.d.Emax = val def getround(self): return self.f.round def setround(self, val): self.f.round = val self.d.rounding = decround[val] prec = property(getprec, setprec) emin = property(getemin, setemin) emax = property(getemax, setemax) round = property(getround, setround) def clear_traps(self): self.f.traps = 0 for trap in self.d.traps: self.d.traps[trap] = False def clear_status(self): self.f.status = 0 self.d.clear_flags() def settraps(self, traplist): # fastdec traplist self.clear_traps() self.f.settraps(traplist) for flag in traplist: self.d.traps[decflags[flag]] = True def setstatus(self, statuslist): # fastdec statuslist self.f.setstatus(statuslist) self.d.clear_flags() for flag in statuslist: self.d.flags[decflags[flag]] = True def gettraps(self): return self.f.gettraps() def getstatus(self): return self.f.getstatus() def status(self): dstatus = 0 for f, v in decflags.iteritems(): if self.d.flags[v]: dstatus |= f return self.f.status, dstatus # We don't want exceptions so that we can compare the status flags. fdc = fastdec.getcontext() fdc.traps = 0 context = Context(fdc) class CdecException(ArithmeticError): def __init__(self, result, funcname, operands): self.errstring = "Error in %s(%s" % (funcname, operands[0]) for op in operands[1:]: self.errstring += ", %s" % op self.errstring += "):\n\n" if isinstance(result, cdec): self.errstring += "mpd: %s\nmpd.to_eng(): %s\ndec: %s\ndec.to_eng_string(): %s\n"\ % (str(result.mpd), result.mpd.to_eng(),\ str(result.dec), result.dec.to_eng_string()) else: self.errstring += "fdresult: %s\ndecresult: %s\n\n" % (str(result[0]), str(result[1])) self.errstring += "%s\n%s\n\n" % (str(context.f), str(context.d)) def __str__(self): return self.errstring class dHandlerCdec: """For cdec return values: Handle known disagreements between decimal.py and fastdec.so. This is just a temporary measure against cluttered output. Detection is crude and possibly unreliable.""" def __init__(self): self.payload_conv_syntax = 0 self.div_impossible = 0 self.div_undefined = 0 self.logb_round_if_gt_prec = 0 self.ulpdiff = 0 self.powmod_zeros = 0 self.total_mag_nan = 0 self.quantize_status = 0 self.max_status = 0 self.round_overflow = 0 def default(self, result, operands, fdstatus, decstatus): """Converting a NaN: ConversionSyntax vs. InvalidOperation""" if result.mpd.is_nan() and \ fdstatus == fastdec.ConversionSyntax and \ decstatus == fastdec.InvalidOperation: self.payload_conv_syntax += 1 return True return False def ulp(self, dec): """Harrison ULP: 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 un_resolve_ulp(self, result, funcname, operands): """Some unary functions may differ from the exact result by less than 1 ULP.""" fdstr = str(result.mpd) fdresult = decimal.Decimal(fdstr) decresult = result.dec deculp = self.ulp(decresult) decop = operands[0].dec tmpctx = context.d.copy() tmpctx.prec *= 2 # result, recalculated at double precision dpresult = getattr(decop, funcname)(context=tmpctx) fddiff = abs(dpresult - fdresult) if fddiff >= deculp: return False # not simply a disagreement, but wrong decdiff = abs(dpresult - decresult) if decdiff >= deculp: return False # not simply a disagreement, but wrong self.ulpdiff += 1 return True def bin_resolve_ulp(self, result, funcname, operands): "Some binary functions may differ from the exact result by less than 1 ULP." fdstr = str(result.mpd) fdresult = decimal.Decimal(fdstr) decresult = result.dec deculp = self.ulp(decresult) op1 = operands[0].dec op2 = operands[1].dec tmpctx = context.d.copy() tmpctx.prec *= 2 # result, recalculated at double precision dpresult = getattr(op1, funcname)(op2, context=tmpctx) fddiff = abs(dpresult - fdresult) if fddiff >= deculp: return False # not simply a disagreement, but wrong decdiff = abs(dpresult - decresult) if decdiff >= deculp: return False # not simply a disagreement, but wrong self.ulpdiff += 1 return True '''def div_generic(self, result, operands, fdstatus, decstatus): """Both results NaN: - DivisionImpossible vs InvalidOperation - DivisionUndefined vs InvalidOperation""" if result.mpd.is_nan() and result.dec.is_nan(): if decstatus == fastdec.InvalidOperation: if fdstatus == fastdec.DivisionImpossible: self.div_impossible += 1 return True if fdstatus == fastdec.DivisionUndefined: self.div_undefined += 1 return True else: return self.default(result, operands, fdstatus, decstatus) __floordiv__ = div_generic __mod__ = div_generic __truediv__ = div_generic divide = div_generic divide_int = div_generic remainder = div_generic remainder_near = div_generic __divmod__ = div_generic divmod = div_generic''' def logb(self, result, operands, fdstatus, decstatus): """Round the result if it is greater than prec.""" if fdstatus&(fastdec.Inexact|fastdec.Rounded) and decstatus == 0: self.logb_round_if_gt_prec += 1 return True return self.default(result, operands, fdstatus, decstatus) def exp(self, result,operands, fdstatus, decstatus): "ULP" if self.default(result, operands, fdstatus, decstatus): return True return self.un_resolve_ulp(result, "exp", operands) def ln(self, result, operands, fdstatus, decstatus): "ULP" if self.default(result, operands, fdstatus, decstatus): return True return self.un_resolve_ulp(result, "ln", operands) def log10(self, result, operands, fdstatus, decstatus): "ULP" if self.default(result, operands, fdstatus, decstatus): return True return self.un_resolve_ulp(result, "log10", operands) def __pow__(self, result, operands, fdstatus, decstatus): """See DIFFERENCES.txt""" if operands[0].mpd.is_zero() and not operands[1].mpd.is_zero() and \ result.mpd == 0 and result.dec.is_nan() and \ fdstatus == 0 and \ decstatus == fastdec.InvalidOperation: self.powmod_zeros += 1 return True elif operands[1].mpd.is_zero() and not operands[0].mpd.is_zero() and \ result.mpd == 1 and result.dec.is_nan() and \ fdstatus == 0 and \ decstatus == fastdec.InvalidOperation: self.powmod_zeros += 1 return True elif fdstatus&fastdec.Rounded and fdstatus&fastdec.Inexact and \ decstatus&fastdec.Rounded and decstatus&fastdec.Inexact: return self.bin_resolve_ulp(result, "__pow__", operands) else: return self.default(result, operands, fdstatus, decstatus) power = __pow__ def compare_total_mag(self, result, operands, fdstatus, decstatus): """See DIFFERENCES.txt""" if operands[0].mpd.is_nan() and operands[1].mpd.is_nan() and \ abs(result.mpd) == 1 and abs(result.dec) == 1: self.total_mag_nan += 1 return True return self.default(result, operands, fdstatus, decstatus) compare_total = compare_total_mag def quantize(self, result, operands, fdstatus, decstatus): """See DIFFERENCES.txt""" if result.mpd.is_nan() and result.dec.is_nan() and \ fdstatus == (fastdec.InvalidOperation|fastdec.Inexact|fastdec.Rounded) and \ decstatus == fastdec.InvalidOperation: self.quantize_status += 1 return True return self.default(result, operands, fdstatus, decstatus) def max(self, result, operands, fdstatus, decstatus): """See DIFFERENCES.txt""" if fdstatus == fastdec.Subnormal and \ decstatus == 0: self.max_status += 1 return True return self.default(result, operands, fdstatus, decstatus) max_mag = max min = max min_mag = max class dHandlerObj(): """For non-decimal return values: Handle known disagreements between decimal.py and fastdec.so. This is just a temporary measure against cluttered output. Detection is crude and unreliable.""" def __init__(self): self.cmp_signal = 0 self.int_none_vs_nan = 0 def default(self, result, operands, fdstatus, decstatus): return False def __eq__(self, result, operands, fdstatus, decstatus): """See DIFFERENCES.txt""" if operands[0].mpd.is_nan() or operands[1].mpd.is_nan(): if result[0] == result[1] and \ fdstatus == fastdec.InvalidOperation and \ decstatus == 0: self.cmp_signal += 1 return True return False __ne__ = __eq__ dhandler_cdec = dHandlerCdec() dhandler_obj = dHandlerObj() def cdec_known_disagreement(result, funcname, operands, fdstatus, decstatus): return getattr(dhandler_cdec, funcname, dhandler_cdec.default)(result, operands, fdstatus, decstatus) def obj_known_disagreement(result, funcname, operands, fdstatus, decstatus): return getattr(dhandler_obj, funcname, dhandler_obj.default)(result, operands, fdstatus, decstatus) # Try to convert an object to a cdec. def to_cdec(obj): if isinstance(obj, cdec): return obj if isinstance(obj, (int, long)): c = cdec() c.mpd = fastdec.mpd(obj) c.dec = context.d.create_decimal(obj) c.verify('to_cdec', (obj,)) return c return NotImplemented def verify(result, funcname, operands): """Verifies that after operation 'funcname' with operand(s) 'operands' result[0] and result[1] as well as the context flags have the same value.""" status = context.status() if result[0] != result[1] or status[0] != status[1]: if obj_known_disagreement(result, funcname, operands, status[0], status[1]): return # known disagreements raise CdecException(result, funcname, operands) class cdec(object): """Joins fastdec.so and decimal.py for redundant calculations with error checking.""" __slots__ = ['mpd', 'dec'] def __new__(cls, value=None): self = object.__new__(cls) self.mpd = None self.dec = None if value is not None: context.clear_status() self.mpd = fastdec.mpd(value) self.dec = context.d.create_decimal(value) self.verify('__xnew__', (value,)) return self def __repr__(self): return "cdec('%s')" % str(self.mpd) def __str__(self): return str(self.mpd) def verify(self, funcname, operands): """Verifies that after operation 'funcname' with operand(s) 'operands' self.mpd and self.dec as well as the context flags have the same value.""" fdstr = str(self.mpd) decstr = str(self.dec) fdstr_eng = self.mpd.to_eng() decstr_eng = self.dec.to_eng_string() status = context.status() if fdstr != decstr or fdstr_eng != decstr_eng or status[0] != status[1]: if cdec_known_disagreement(self, funcname, operands, status[0], status[1]): return # known disagreements raise CdecException(self, funcname, operands) def unaryfunc(self, funcname): "unary function returning a cdec" context.clear_status() c = cdec() c.mpd = getattr(self.mpd, funcname)() c.dec = getattr(self.dec, funcname)(context=context.d) c.verify(funcname, (self,)) return c def unaryfunc_noctx(self, funcname): "unary function returning a cdec" context.clear_status() c = cdec() c.mpd = getattr(self.mpd, funcname)() c.dec = getattr(self.dec, funcname)() c.verify(funcname, (self,)) return c def unaryfunc_ctx(self, funcname): "unary function returning a cdec, uses the context methods of decimal.py" context.clear_status() c = cdec() c.mpd = getattr(self.mpd, funcname)() c.dec = getattr(context.d, funcname)(self.dec) c.verify(funcname, (self,)) return c def obj_unaryfunc_noctx(self, funcname): "unary function returning a cdec" context.clear_status() r_mpd = getattr(self.mpd, funcname)() r_dec = getattr(self.dec, funcname)() verify((r_mpd, r_dec), funcname, (self,)) return r_mpd def obj_unaryfunc(self, funcname): "unary function returning an object other than a cdec" context.clear_status() r_mpd = getattr(self.mpd, funcname)() r_dec = getattr(self.dec, funcname)(context=context.d) verify((r_mpd, r_dec), funcname, (self,)) return r_mpd def obj_unaryfunc_ctx(self, funcname): "unary function returning an object other than a cdec" context.clear_status() r_mpd = getattr(self.mpd, funcname)() r_dec = getattr(context.d, funcname)(self.dec) verify((r_mpd, r_dec), funcname, (self,)) return r_mpd def binaryfunc(self, other, funcname): "binary function returning a cdec" context.clear_status() c = cdec() other_mpd = other_dec = other if isinstance(other, cdec): other_mpd = other.mpd other_dec = other.dec c.mpd = getattr(self.mpd, funcname)(other_mpd) c.dec = getattr(self.dec, funcname)(other_dec, context=context.d) c.verify(funcname, (self, other)) return c def binaryfunc_ctx(self, other, funcname): "binary function returning a cdec, uses the context methods of decimal.py" context.clear_status() c = cdec() other_mpd = other_dec = other if isinstance(other, cdec): other_mpd = other.mpd other_dec = other.dec c.mpd = getattr(self.mpd, funcname)(other_mpd) c.dec = getattr(context.d, funcname)(self.dec, other_dec) c.verify(funcname, (self, other)) return c def binaryfunc_noctx(self, other, funcname): "binary function returning a cdec, uses the context methods of decimal.py" context.clear_status() c = cdec() other_mpd = other_dec = other if isinstance(other, cdec): other_mpd = other.mpd other_dec = other.dec c.mpd = getattr(self.mpd, funcname)(other_mpd) c.dec = getattr(self.dec, funcname)(other_dec) c.verify(funcname, (self, other)) return c def obj_binaryfunc(self, other, funcname): "binary function returning an object other than a cdec" context.clear_status() other_mpd = other_dec = other if isinstance(other, cdec): other_mpd = other.mpd other_dec = other.dec r_mpd = getattr(self.mpd, funcname)(other_mpd) r_dec = getattr(self.dec, funcname)(other_dec, context=context.d) verify((r_mpd, r_dec), funcname, (self, other)) return r_mpd def obj_binaryfunc_noctx(self, other, funcname): "binary function returning an object other than a cdec" context.clear_status() other_mpd = other_dec = other if isinstance(other, cdec): other_mpd = other.mpd other_dec = other.dec r_mpd = getattr(self.mpd, funcname)(other_mpd) r_dec = getattr(self.dec, funcname)(other_dec) verify((r_mpd, r_dec), funcname, (self, other)) return r_mpd def ternaryfunc(self, other, third, funcname): "ternary function returning a cdec" context.clear_status() c = cdec() other_mpd = other_dec = other if isinstance(other, cdec): other_mpd = other.mpd other_dec = other.dec third_mpd = third_dec = third if isinstance(third, cdec): third_mpd = third.mpd third_dec = third.dec c.mpd = getattr(self.mpd, funcname)(other_mpd, third_mpd) c.dec = getattr(self.dec, funcname)(other_dec, third_dec, context=context.d) c.verify(funcname, (self, other, third)) return c def ternaryfunc_ctx(self, other, third, funcname): "ternary function returning a cdec, uses the context methods of decimal.py" context.clear_status() c = cdec() other_mpd = other_dec = other if isinstance(other, cdec): other_mpd = other.mpd other_dec = other.dec third_mpd = third_dec = third if isinstance(third, cdec): third_mpd = third.mpd third_dec = third.dec if funcname == 'power': if (third is not None): c.mpd = getattr(self.mpd, 'powmod')(other_mpd, third_mpd) else: c.mpd = getattr(self.mpd, 'pow')(other_mpd) else: c.mpd = getattr(self.mpd, funcname)(other_mpd, third_mpd) c.dec = getattr(context.d, funcname)(self.dec, other_dec, third_dec) c.verify(funcname, (self, other, third)) return c def __abs__(self): return self.unaryfunc('__abs__') def __add__(self, other): return self.binaryfunc(other, '__add__') def __copy__(self): return self.unaryfunc_noctx('__copy__') def __deepcopy__(self, memo=None): context.clear_status() c = cdec() c.mpd = self.mpd.__deepcopy__(memo) c.dec = self.dec.__deepcopy__(memo) c.verify('__deepcopy__', (self,)) return c def __divmod__(self, other): context.clear_status() q = cdec() r = cdec() other_mpd = other_dec = other if isinstance(other, cdec): other_mpd = other.mpd other_dec = other.dec q.mpd, r.mpd = self.mpd.__divmod__(other_mpd) q.dec, r.dec = self.dec.__divmod__(other_dec, context.d) q.verify('__divmod__', (self, other)) r.verify('__divmod__', (self, other)) return (q, r) def __eq__(self, other): return self.obj_binaryfunc_noctx(other, '__eq__') def __float__(self): if (self.mpd.is_nan() and self.dec.is_nan()): return float("NaN") try: return self.obj_unaryfunc_noctx('__float__') except ValueError: return None def __floordiv__(self, other): return self.binaryfunc(other, '__floordiv__') def __ge__(self, other): return self.obj_binaryfunc_noctx(other, '__ge__') def __gt__(self, other): return self.obj_binaryfunc_noctx(other, '__gt__') def __hash__(self): global PY25_HASH_HAVE_WARNED if self.mpd.is_nan(): return cdec(0) # for testing raise TypeError('Cannot hash a NaN value.') ret = None try: # Python 2.5 can use exorbitant amounts of memory ret = self.obj_unaryfunc_noctx('__hash__') except MemoryError: if not PY25_HASH_HAVE_WARNED: sys.stderr.write("Out of memory while hashing %s: upgrade to Python 2.6\n" % str(self.mpd)) PY25_HASH_HAVE_WARNED = 1 return ret def __int__(self): # ValueError or OverflowError if self.mpd.is_special(): return (None, None) return self.obj_unaryfunc_noctx('__int__') def __le__(self, other): return self.obj_binaryfunc(other, '__le__') def __long__(self): # ValueError or OverflowError if self.mpd.is_special(): return (None, None) return self.obj_unaryfunc_noctx('__long__') def __lt__(self, other): return self.obj_binaryfunc(other, '__lt__') def __mod__(self, other): return self.binaryfunc(other, '__mod__') def __mul__(self, other): return self.binaryfunc(other, '__mul__') def __ne__(self, other): return self.obj_binaryfunc_noctx(other, '__ne__') def __neg__(self): return self.unaryfunc('__neg__') def __nonzero__(self): return self.obj_unaryfunc_noctx('__nonzero__') def __pos__(self): return self.unaryfunc('__pos__') def __pow__(self, other, mod=None): return self.ternaryfunc(other, mod, '__pow__') # __reduce__ def __sub__(self, other): return self.binaryfunc(other, '__sub__') def __truediv__(self, other): return self.binaryfunc(other, '__truediv__') __div__ = __truediv__ def __trunc__(self): # ValueError or OverflowError if self.mpd.is_special(): return (None, None) return self.obj_unaryfunc_noctx('__trunc__') def abs(self): return self.unaryfunc_ctx('abs') def add(self, other): return self.binaryfunc_ctx(other, 'add') def canonical(self): return self.unaryfunc_noctx('canonical') def compare(self, other): return self.binaryfunc_noctx(other, 'compare') def compare_signal(self, other): return self.binaryfunc_noctx(other, 'compare_signal') def compare_total(self, other): return self.binaryfunc_noctx(other, 'compare_total') def compare_total_mag(self, other): return self.binaryfunc_noctx(other, 'compare_total_mag') def copy_abs(self): return self.unaryfunc_noctx('copy_abs') # copy_decimal def copy_negate(self): return self.unaryfunc_noctx('copy_negate') def copy_sign(self, other): return self.binaryfunc_noctx(other, 'copy_sign') def divide(self, other): return self.binaryfunc_ctx(other, 'divide') def divide_int(self, other): return self.binaryfunc_ctx(other, 'divide_int') def divmod(self, other): context.clear_status() q = cdec() r = cdec() other_mpd = other_dec = other if isinstance(other, cdec): other_mpd = other.mpd other_dec = other.dec q.mpd, r.mpd = self.mpd.divmod(other_mpd) q.dec, r.dec = context.d.divmod(self.dec, other_dec) q.verify('divmod', (self, other)) r.verify('divmod', (self, other)) return (q, r) def exp(self): return self.unaryfunc('exp') def fma(self, other, third): return self.ternaryfunc(other, third, 'fma') # imag # invroot def is_canonical(self): return self.obj_unaryfunc_noctx('is_canonical') def is_finite(self): return self.obj_unaryfunc_noctx('is_finite') def is_infinite(self): return self.obj_unaryfunc_noctx('is_infinite') def is_nan(self): return self.obj_unaryfunc_noctx('is_nan') def is_normal(self): return self.obj_unaryfunc('is_normal') def is_qnan(self): return self.obj_unaryfunc_noctx('is_qnan') def is_signed(self): return self.obj_unaryfunc_noctx('is_signed') def is_snan(self): return self.obj_unaryfunc_noctx('is_snan') def is_subnormal(self): return self.obj_unaryfunc('is_subnormal') def is_zero(self): return self.obj_unaryfunc_noctx('is_zero') def ln(self): return self.unaryfunc('ln') def log10(self): return self.unaryfunc('log10') def logb(self): return self.unaryfunc('logb') def logical_and(self, other): return self.binaryfunc_ctx(other, 'logical_and') def logical_invert(self): return self.unaryfunc_ctx('logical_invert') def logical_or(self, other): return self.binaryfunc_ctx(other, 'logical_or') def logical_xor(self, other): return self.binaryfunc_ctx(other, 'logical_xor') def max(self, other): return self.binaryfunc(other, 'max') def max_mag(self, other): return self.binaryfunc(other, 'max_mag') def min(self, other): return self.binaryfunc(other, 'min_mag') def min_mag(self, other): return self.binaryfunc(other, 'min_mag') def minus(self): return self.unaryfunc_ctx('minus') def multiply(self, other): return self.binaryfunc_ctx(other, 'multiply') def next_minus(self): return self.unaryfunc('next_minus') def next_plus(self): return self.unaryfunc('next_plus') def next_toward(self, other): return self.binaryfunc(other, 'next_toward') def normalize(self): return self.unaryfunc('normalize') def number_class(self): return self.obj_unaryfunc('number_class') def plus(self): return self.unaryfunc_ctx('plus') def power(self, other, mod=None): return self.ternaryfunc_ctx(other, mod, 'power') # powmod: same as __pow__ or power with three arguments def quantize(self, other): return self.binaryfunc(other, 'quantize') def radix(self): return self.obj_unaryfunc_noctx('radix') # real # reduce: same as normalize def remainder(self, other): return self.binaryfunc_ctx(other, 'remainder') def remainder_near(self, other): return self.binaryfunc(other, 'remainder_near') def rotate(self, other): return self.binaryfunc(other, 'rotate') def same_quantum(self, other): return self.obj_binaryfunc_noctx(other, 'same_quantum') def scaleb(self, other): return self.binaryfunc(other, 'scaleb') def shift(self, other): return self.binaryfunc(other, 'shift') # sign def sqrt(self): return self.unaryfunc('sqrt') def subtract(self, other): return self.binaryfunc_ctx(other, 'subtract') def to_eng_string(self): return self.obj_unaryfunc('to_eng_string') def to_integral(self): return self.unaryfunc('to_integral') def to_integral_exact(self): return self.unaryfunc('to_integral_exact') def to_integral_value(self): return self.unaryfunc('to_integral_value') def to_sci_string(self): return self.obj_unaryfunc_ctx('to_sci_string') __radd__ = __add__ __rmul__ = __mul__ def __rsub__(self, other): return other.__sub__(self) def __rtruediv__(self, other): return other.__truediv__(self) __rdiv__ = __rtruediv__ def __rdivmod__(self, other): return other.__divmod__(self) def __rmod__(self, other): return other.__mod__(self) def __rfloordiv__(self, other): return other.__floordiv__(self) def __rpow__(self, other): return other.__pow__(self) 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_unary(method, prec_lst, iter): log("testing %s ...", method) for prec in prec_lst: log(" prec: %d", prec) context.prec = prec for round in sorted(decround): context.round = round rprec = 10**prec exprange = fastdec.MAX_EMAX if method in ['__int__', '__long__', '__trunc__', 'to_integral', \ 'to_integral_value', 'to_integral_value']: exprange = 9999 if py_minor == 5 and method == '__hash__': exprange = 9999 for a in un_close_to_pow10(prec, exprange, iter): try: x = cdec(a) getattr(x, method)() except CdecException, err: log(err) for a in un_close_numbers(prec, exprange, -exprange, iter): try: x = cdec(a) getattr(x, method)() except CdecException, err: log(err) for a in un_incr_digits(prec, exprange, iter): try: x = cdec(a) getattr(x, method)() except CdecException, err: log(err) for i in range(1000): try: s = randdec(prec, exprange) x = cdec(s) getattr(x, method)() except CdecException, err: log(err) except OverflowError: pass def test_un_logical(method, prec_lst, iter): log("testing %s ...", method) for prec in prec_lst: log(" prec: %d", prec) context.prec = prec for round in sorted(decround): context.round = round for a in logical_un_incr_digits(prec, iter): try: x = cdec(a) getattr(x, method)() except CdecException, err: log(err) for i in range(1000): try: s = randdec(prec, 999999) x = cdec(s) getattr(x, method)() except CdecException, err: log(err) except OverflowError: pass def test_binary(method, prec_lst, iter): log("testing %s ...", method) for prec in prec_lst: log(" prec: %d", prec) context.prec = prec for round in sorted(decround): context.round = round exprange = fastdec.MAX_EMAX if method in ['__pow__', 'power']: exprange = 99999 for a, b in bin_close_to_pow10(prec, exprange, iter): try: x = cdec(a) y = cdec(b) getattr(x, method)(y) except CdecException, err: log(err) for a, b in bin_close_numbers(prec, exprange, -exprange, iter): try: x = cdec(a) y = cdec(b) getattr(x, method)(y) except CdecException, err: log(err) for a, b in bin_incr_digits(prec, exprange, iter): try: x = cdec(a) y = cdec(b) getattr(x, method)(y) except CdecException, err: log(err) for i in range(1000): s1 = randdec(prec, exprange) s2 = randdec(prec, exprange) try: x = cdec(s1) y = cdec(s2) getattr(x, method)(y) except CdecException, err: log(err) def test_bin_logical(method, prec_lst, iter): log("testing %s ...", method) for prec in prec_lst: log(" prec: %d", prec) context.prec = prec for round in sorted(decround): context.round = round for a, b in logical_bin_incr_digits(prec, iter): try: x = cdec(a) y = cdec(b) getattr(x, method)(y) except CdecException, err: log(err) for i in range(1000): s1 = randdec(prec, 999999) s2 = randdec(prec, 999999) try: x = cdec(s1) y = cdec(s2) getattr(x, method)(y) except CdecException, err: log(err) def test_ternary(method, prec_lst, iter): log("testing %s ...", method) for prec in prec_lst: log(" prec: %d", prec) context.prec = prec for round in sorted(decround): context.round = round exprange = fastdec.MAX_EMAX if method in ['__pow__', 'power']: exprange = 99999 for a, b, c in tern_close_numbers(prec, exprange, -exprange, iter): try: x = cdec(a) y = cdec(b) z = cdec(c) getattr(x, method)(y, z) except CdecException, err: log(err) for a, b, c in tern_incr_digits(prec, exprange, iter): try: x = cdec(a) y = cdec(b) z = cdec(c) getattr(x, method)(y, z) except CdecException, err: log(err) for i in range(1000): s1 = randdec(prec, 2*exprange) s2 = randdec(prec, 2*exprange) s3 = randdec(prec, 2*exprange) try: x = cdec(s1) y = cdec(s2) z = cdec(s3) getattr(x, method)(y, z) except CdecException, err: log(err) def test_format(prec_lst, iter): log("testing format") for prec in prec_lst: log(" prec: %d", prec) context.prec = prec for round in sorted(decround): context.round = round for a in un_incr_digits(prec, 9999, iter): try: fmt = gen_format() x = format(context.f.create_decimal(a), fmt) y = format(context.d.create_decimal(a), fmt) except Exception, err: print err, fmt continue # desired behavior is not clear if "NaN" in x and "NaN" in y: continue if x != y: print context.f print context.d print "\n%s %s" % (a, fmt) print "%s %s\n" % (x, y) exit(1) for i in range(1000): try: a = randdec(99, 9999) fmt = gen_format() x = format(context.f.create_decimal(a), fmt) y = format(context.d.create_decimal(a), fmt) except Exception, err: print err, fmt continue # desired behavior is not clear if "NaN" in x and "NaN" in y: continue if x != y: print "\n%s %s" % (a, fmt) print "%s %s\n" % (x, y) exit(1) if __name__ == '__main__': from randdec import * import time import sys samples = 1 iter = 1 if '--short' in sys.argv: samples = 1 iter = 1 elif '--medium' in sys.argv: samples = 1 iter = None elif '--long' in sys.argv: samples = 5 iter = None elif '--all' in sys.argv: samples = 100 iter = None x = int(time.time()) random.seed(x) log("\nRandom seed: %d\n\n", x) # List of what we aren't checking: notests = [] for method in fdall: if not method in fdunary and \ not method in fdbinary and \ not method in fdternary: notests.append(method) log("Skipping tests: \n\n%s\n", str(notests)) for method in ['__floordiv__', '__mod__', '__truediv__', 'divide', 'divide_int', 'remainder', 'remainder_near', '__divmod__', 'divmod']: prec_lst = random.sample(range(1, 101), samples) test_binary(method, prec_lst, iter) for method in sorted(fdunary): prec_lst = random.sample(range(1, 101), samples) test_unary(method, prec_lst, iter) for method in sorted(fdbinary): prec_lst = random.sample(range(1, 101), samples) test_binary(method, prec_lst, iter) for method in sorted(fdternary): prec_lst = random.sample(range(1, 101), samples) test_ternary(method, prec_lst, iter) prec_lst = random.sample(range(1, 101), samples) test_un_logical('logical_invert', prec_lst, iter) for method in ['logical_and', 'logical_or', 'logical_xor']: prec_lst = random.sample(range(1, 101), samples) test_bin_logical(method, prec_lst, iter) if py_minor >= 7: # some tests will fail with 2.6, since alignment has been changed # in decimal.py 2.7. test_format(range(1, 20), 10)