blob: 24f4d24d6c7cdad4b11c864d9e2589522d2bfd09 [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright (c) 2008-2010 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.
#
#
# The cdec (for "check decimal") class checks cdecimal.so against decimal.py.
# A cdec object consists of a cdecimal.Decimal and a decimal.Decimal. Every
# operation is carried out on both types. If the results don't match, an
# exception is raised.
#
# Usage: python deccheck.py [--short|--medium|--long|--all]
#
import cdecimal, decimal
import sys, inspect
import array
import random
from copy import copy
from randdec import *
EXIT_STATUS = 0
# Python 2.5 can require exorbitant amounts of memory for hashing integers.
PY25_HASH_HAVE_WARNED = 0
# Python 2.5 bug in _dlog10.
PY25_DLOG10_HAVE_WARNED = 0
py_minor = sys.version_info[1]
py_micro = sys.version_info[2]
if py_minor >= 7:
from randfloat import *
HAVE_FORMATHELPER = True
try:
from formathelper import rand_format, rand_locale
except:
HAVE_FORMATHELPER = False
if py_minor == 5 and py_micro <= 4:
# decimal.py has additional bugs, not worth sorting them out individually.
sys.stderr.write("""
#
# Error: deccheck2.py tests cdecimal against decimal.py. For several Python
# versions, deccheck2.py suppresses known bugs in decimal.py in the output.
# In versions 2.5.0 through 2.5.4, decimal.py has additional bugs which are
# not discarded, so the output is too verbose to be useful.
#
# cdecimal should work fine, nevertheless it is recommended to upgrade
# Python to version 2.5.5 or 2.6.6.
#
""")
sys.exit(1)
# Translate symbols.
deccond = {
cdecimal.Clamped: decimal.Clamped,
cdecimal.ConversionSyntax: decimal.ConversionSyntax,
cdecimal.DivisionByZero: decimal.DivisionByZero,
cdecimal.DivisionImpossible: decimal.InvalidOperation,
cdecimal.DivisionUndefined: decimal.DivisionUndefined,
cdecimal.Inexact: decimal.Inexact,
cdecimal.InvalidContext: decimal.InvalidContext,
cdecimal.InvalidOperation: decimal.InvalidOperation,
cdecimal.Overflow: decimal.Overflow,
cdecimal.Rounded: decimal.Rounded,
cdecimal.Subnormal: decimal.Subnormal,
cdecimal.Underflow: decimal.Underflow,
}
mpdcond = {
decimal.Clamped: cdecimal.Clamped,
decimal.ConversionSyntax: cdecimal.ConversionSyntax,
decimal.DivisionByZero: cdecimal.DivisionByZero,
decimal.InvalidOperation: cdecimal.DivisionImpossible,
decimal.DivisionUndefined: cdecimal.DivisionUndefined,
decimal.Inexact: cdecimal.Inexact,
decimal.InvalidContext: cdecimal.InvalidContext,
decimal.InvalidOperation: cdecimal.InvalidOperation,
decimal.Overflow: cdecimal.Overflow,
decimal.Rounded: cdecimal.Rounded,
decimal.Subnormal: cdecimal.Subnormal,
decimal.Underflow: cdecimal.Underflow
}
decround = {
cdecimal.ROUND_UP: decimal.ROUND_UP,
cdecimal.ROUND_DOWN: decimal.ROUND_DOWN,
cdecimal.ROUND_CEILING: decimal.ROUND_CEILING,
cdecimal.ROUND_FLOOR: decimal.ROUND_FLOOR,
cdecimal.ROUND_HALF_UP: decimal.ROUND_HALF_UP,
cdecimal.ROUND_HALF_DOWN: decimal.ROUND_HALF_DOWN,
cdecimal.ROUND_HALF_EVEN: decimal.ROUND_HALF_EVEN,
cdecimal.ROUND_05UP: decimal.ROUND_05UP
}
class Context(object):
"""Provides a convenient way of syncing the cdecimal and decimal contexts"""
__slots__ = ['f', 'd']
def __init__(self, mpdctx=cdecimal.getcontext()):
"""Initialization is from the cdecimal context"""
self.f = mpdctx
self.d = decimal.getcontext()
self.d.prec = self.f.prec
self.d.Emin = self.f.Emin
self.d.Emax = self.f.Emax
self.d.rounding = decround[self.f.rounding]
self.d.capitals = self.f.capitals
self.settraps([sig for sig in self.f.traps if self.f.traps[sig]])
self.setstatus([sig for sig in self.f.flags if self.f.flags[sig]])
self.d._clamp = self.f._clamp
def getprec(self):
assert(self.f.prec == self.d.prec)
return self.f.prec
def setprec(self, val):
self.f.prec = val
self.d.prec = val
def getemin(self):
assert(self.f.Emin == self.d.Emin)
return self.f.Emin
def setemin(self, val):
self.f.Emin = val
self.d.Emin = val
def getemax(self):
assert(self.f.Emax == self.d.Emax)
return self.f.Emax
def setemax(self, val):
self.f.Emax = val
self.d.Emax = val
def getround(self):
return self.d.rounding
def setround(self, val):
self.f.rounding = val
self.d.rounding = decround[val]
def getcapitals(self):
assert(self.f.capitals == self.d.capitals)
return self.f.capitals
def setcapitals(self, val):
self.f.capitals = val
self.d.capitals = val
def getclamp(self):
assert(self.f._clamp == self.d._clamp)
return self.f._clamp
def setclamp(self, val):
self.f._clamp = val
self.d._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.f.clear_traps()
for trap in self.d.traps:
self.d.traps[trap] = False
def clear_status(self):
self.f.clear_flags()
self.d.clear_flags()
def settraps(self, lst): # cdecimal signal list
self.clear_traps()
for signal in lst:
self.f.traps[signal] = True
self.d.traps[deccond[signal]] = True
def setstatus(self, lst): # cdecimal signal list
self.clear_status()
for signal in lst:
self.f.flags[signal] = True
self.d.flags[deccond[signal]] = True
def assert_eq_status(self):
"""assert equality of cdecimal and decimal status"""
for signal in self.f.flags:
if signal == cdecimal.FloatOperation:
continue
if self.f.flags[signal] == (not self.d.flags[deccond[signal]]):
return False
return True
# We don't want exceptions so that we can compare the status flags.
context = Context()
context.Emin = cdecimal.MIN_EMIN
context.Emax = cdecimal.MAX_EMAX
context.clear_traps()
# When creating decimals, cdecimal is ultimately limited by the maximum
# context values. We emulate this restriction for decimal.py.
maxcontext = decimal.Context(
prec=cdecimal.MAX_PREC,
Emin=cdecimal.MIN_EMIN,
Emax=cdecimal.MAX_EMAX,
rounding=decimal.ROUND_HALF_UP,
capitals=1
)
maxcontext._clamp = 0
def decimal_new(value):
maxcontext.traps = copy(context.d.traps)
maxcontext.clear_flags()
dec = maxcontext.create_decimal(value)
if maxcontext.flags[decimal.Inexact] or \
maxcontext.flags[decimal.Rounded]:
dec = decimal.Decimal("NaN")
context.d.flags[decimal.InvalidOperation] = True
return dec
_exc_fmt = "\
cdecimal_sci: %s\n\
decimal_sci: %s\n\
cdecimal_eng: %s\n\
decimal_eng: %s\n"
_exc_fmt_tuple = "\
cdecimal_tuple: %s\n\
decimal_tuple: %s\n"
_exc_fmt_obj = "\
cdecimal: %s\n\
decimal: %s\n\n"
class CdecException(ArithmeticError):
def __init__(self, result, funcname, operands, fctxstr, dctxstr):
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 += _exc_fmt % (str(result.mpd),
str(result.dec),
result.mpd.to_eng(),
result.dec.to_eng_string())
mpd_tuple = result.mpd.as_tuple()
dec_tuple = result.dec.as_tuple()
if mpd_tuple != dec_tuple:
self.errstring += _exc_fmt_tuple % (str(mpd_tuple),
str(dec_tuple))
else:
self.errstring += _exc_fmt_obj % (str(result[0]), str(result[1]))
self.errstring += "%s\n%s\n\n" % (fctxstr, dctxstr)
def __str__(self):
return self.errstring
class dHandlerCdec:
"""For cdec return values:
Handle known disagreements between decimal.py and cdecimal.so.
This is just a temporary measure against cluttered output.
Detection is crude and possibly unreliable."""
def __init__(self):
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.maxctx = decimal.Context(Emax=10**18, Emin=-10**18)
def default(self, result, operands):
return False
def harrison_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 standard_ulp(self, dec, prec):
return decimal._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."""
d = decimal
cmp = 1 if x.compare_total(d.Decimal("+0")) >= 0 else -1
if mode in (d.ROUND_HALF_EVEN, d.ROUND_HALF_UP, d.ROUND_HALF_DOWN):
return 0
elif mode == d.ROUND_CEILING:
return 1
elif mode == d.ROUND_FLOOR:
return -1
elif mode == d.ROUND_UP:
return cmp
elif mode == d.ROUND_DOWN:
return -cmp
elif mode == d.ROUND_05UP:
return 2
else:
raise ValueError("Unexpected rounding mode: %s" % mode)
def check_ulpdiff(self, exact, rounded):
# current precision
p = context.d.prec
# Convert infinities to the largest representable number + 1.
x = exact
if exact.is_infinite():
x = decimal._dec_from_triple(exact._sign, '10', context.d.Emax)
y = rounded
if rounded.is_infinite():
y = decimal._dec_from_triple(rounded._sign, '10', context.d.Emax)
# err = (rounded - exact) / ulp(rounded)
self.maxctx.prec = p * 2
t = self.maxctx.subtract(y, x)
if context.f._flags & cdecimal.DecClamped or \
context.f._flags & cdecimal.DecUnderflow:
# 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)
d = decimal
dir = self.rounding_direction(x, context.d.rounding)
if dir == 0:
if d.Decimal("-0.6") < err < d.Decimal("0.6"):
return True
elif dir == 1: # directed, upwards
if d.Decimal("-0.1") < err < d.Decimal("1.1"):
return True
elif dir == -1: # directed, downwards
if d.Decimal("-1.1") < err < d.Decimal("0.1"):
return True
else: # ROUND_05UP
if d.Decimal("-1.1") < err < d.Decimal("1.1"):
return True
print("ulp: %s error: %s exact: %s mpd_rounded: %s"
% (ulp, err, exact, rounded))
return False
def un_resolve_ulp(self, result, funcname, operands):
"""Check if results of cdecimal's exp, ln and log10 functions are
within the allowed ulp ranges. This function is only called if
context.f._allcr is 0."""
# "exact" result, double precision, half_even
self.maxctx.prec = context.d.prec * 2
op = operands[0].dec
exact = getattr(op, funcname)(context=self.maxctx)
# cdecimal's rounded result
s = str(result.mpd)
rounded = decimal.Decimal(s)
self.ulpdiff += 1
return self.check_ulpdiff(exact, rounded)
def bin_resolve_ulp(self, result, funcname, operands):
"""Check if results of cdecimal's power function are within the
allowed ulp ranges."""
# "exact" result, double precision, half_even
self.maxctx.prec = context.d.prec * 2
op1 = operands[0].dec
op2 = operands[1].dec
exact = getattr(op1, funcname)(op2, context=self.maxctx)
# cdecimal's rounded result
s = str(result.mpd)
rounded = decimal.Decimal(s)
self.ulpdiff += 1
return self.check_ulpdiff(exact, rounded)
def resolve_underflow(self, result):
"""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 str(result.mpd) != str(result.dec):
return False # Results must be identical.
if context.f.flags[cdecimal.Rounded] and \
context.f.flags[cdecimal.Inexact] and \
context.d.flags[decimal.Rounded] and \
context.d.flags[decimal.Inexact]:
return True # Subnormal/Underflow may be missing.
return False
def exp(self, result, operands):
if result.mpd.is_nan() or result.dec.is_nan():
return False
if context.f._allcr:
return self.resolve_underflow(result)
return self.un_resolve_ulp(result, "exp", operands)
def log10(self, result, operands):
if result.mpd.is_nan() or result.dec.is_nan():
return False
if context.f._allcr:
return self.resolve_underflow(result)
return self.un_resolve_ulp(result, "log10", operands)
def ln(self, result, operands):
if result.mpd.is_nan() or result.dec.is_nan():
return False
if context.f._allcr:
return self.resolve_underflow(result)
return self.un_resolve_ulp(result, "ln", operands)
def __pow__(self, result, operands):
if operands[2] is not None: # three argument __pow__
# issue7049: third arg must fit into precision
if (operands[0].mpd.is_zero() != operands[1].mpd.is_zero()):
if (result.mpd == 0 or result.mpd == 1) and result.dec.is_nan():
if (not context.f.flags[cdecimal.InvalidOperation]) and \
context.d.flags[decimal.InvalidOperation]:
self.powmod_zeros += 1
return True
# issue7049: ideal exponent
if decimal.Decimal(str(result.mpd)) == result.dec:
return True
elif result.mpd.is_nan() or result.dec.is_nan():
return False
elif context.f.flags[cdecimal.Rounded] and \
context.f.flags[cdecimal.Inexact] and \
context.d.flags[decimal.Rounded] and \
context.d.flags[decimal.Inexact]:
# decimal.py: correctly-rounded pow()
return self.bin_resolve_ulp(result, "__pow__", operands)
else:
return False
power = __pow__
def __radd__(self, result, operands):
"""decimal.py gives preference to the first nan"""
if operands[0].mpd.is_nan() and operands[1].mpd.is_nan() and \
result.mpd.is_nan() and result.dec.is_nan():
return True
return False
__rmul__ = __radd__
# Fixed in 2.7.2.
def plus(self, result, operands):
"""special cases for zero/ROUND_FLOOR"""
if context.f.rounding == cdecimal.ROUND_FLOOR:
if operands[0].mpd.is_zero():
return True
return False
minus = __neg__ = __pos__ = plus
if py_minor <= 6:
def rotate(self, result, operands):
"""truncate excess digits before the operation"""
if len(operands[0].dec._int) > context.f.prec:
return True
return False
shift = rotate
def compare_total_mag(self, result, operands):
"""fixed in Python2.6.?"""
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 False
compare_total = compare_total_mag
def logb(self, result, operands):
"""fixed in Python2.6.?"""
if context.f.flags[cdecimal.Rounded] and \
(not context.d.flags[decimal.Rounded]):
self.logb_round_if_gt_prec += 1
return True
return False
def max(self, result, operands):
if py_minor <= 5 or py_micro <= 1:
# broken in multiple ways, fixed in 2.6.2
return True
# hack, since is_nan() appears to be broken on the result
if (not result.mpd.is_nan()) and 'sNaN' in result.dec.to_eng_string():
return True
if context.f.flags[cdecimal.Subnormal] and \
(not context.d.flags[decimal.Subnormal]):
self.max_status += 1
return True
return False
max_mag = max
min = max
min_mag = max
class dHandlerObj():
"""For non-decimal return values:
Handle known disagreements between decimal.py and cdecimal.so."""
def __init__(self):
pass
def default(self, result, operands):
return False
__ge__ = __gt__ = __le__ = __lt__ = __str__ = __repr__ = default
if py_minor >= 7:
__ne__ = __eq__ = default
if py_minor <= 6:
def __eq__(self, result, operands):
"""cdecimal raises for all sNaN comparisons"""
if operands[0].mpd.is_snan() or operands[1].mpd.is_snan():
return True
return False
__ne__ = __eq__
if py_minor <= 6:
# Fixed in release26-maint, but a lot of distributed
# versions do not have the fix yet.
def is_normal(self, result, operands):
# Issue7099
if operands[0].mpd.is_normal():
return True
return False
if py_minor <= 5:
"""decimal.py uses double quotes instead of single quotes."""
def __repr__(self, result, operands):
return True
dhandler_cdec = dHandlerCdec()
def cdec_known_disagreement(result, funcname, operands):
return getattr(dhandler_cdec, funcname, dhandler_cdec.default)(result, operands)
dhandler_obj = dHandlerObj()
def obj_known_disagreement(result, funcname, operands):
return getattr(dhandler_obj, funcname, dhandler_obj.default)(result, operands)
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
values."""
global EXIT_STATUS
if result[0] != result[1] or not context.assert_eq_status():
if obj_known_disagreement(result, funcname, operands):
return # skip known disagreements
EXIT_STATUS = 1
raise CdecException(result, funcname, operands,
str(context.f), str(context.d))
class cdec(object):
"""Joins cdecimal.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()
if py_minor <= 6 and isinstance(value, float):
self.mpd = cdecimal.Decimal.from_float(value)
self.dec = decimal.Decimal.from_float(value)
else:
self.mpd = cdecimal.Decimal(value)
self.dec = decimal_new(value)
self.verify('__xnew__', (value,))
return self
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
values."""
global EXIT_STATUS
mpdstr = str(self.mpd)
decstr = str(self.dec)
mpdstr_eng = self.mpd.to_eng_string()
decstr_eng = self.dec.to_eng_string()
mpd_tuple = self.mpd.as_tuple()
dec_tuple = self.dec.as_tuple()
if mpd_tuple != dec_tuple: # XXX
if mpd_tuple[2] == 'F' and dec_tuple[2] == 'F' and \
mpd_tuple[1] == () and dec_tuple[1] == (0,):
return
if mpdstr != decstr or mpdstr_eng != decstr_eng or mpd_tuple != dec_tuple \
or not context.assert_eq_status():
if cdec_known_disagreement(self, funcname, operands):
return # skip known disagreements
EXIT_STATUS = 1
raise CdecException(self, funcname, operands,
str(context.f), str(context.d))
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)()
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(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)()
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)
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 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)
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)
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('__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 __div__(self, other):
return self.binaryfunc(other, '__div__')
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(other, '__eq__')
def __float__(self):
if (self.mpd.is_nan() and self.dec.is_nan()):
return float("NaN")
try:
return self.obj_unaryfunc('__float__')
except ValueError:
return None
def __floordiv__(self, other):
return self.binaryfunc(other, '__floordiv__')
def __ge__(self, other):
return self.obj_binaryfunc(other, '__ge__')
def __gt__(self, other):
return self.obj_binaryfunc(other, '__gt__')
def __hash__(self):
global PY25_HASH_HAVE_WARNED
if self.mpd.is_snan() or (py_minor <= 6 and self.mpd.is_nan()):
return None # for testing
raise ValueError('Cannot hash a NaN value.')
ret = None
try: # Python 2.5 can use exorbitant amounts of memory
ret = self.obj_unaryfunc('__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('__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('__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(other, '__ne__')
def __neg__(self):
return self.unaryfunc('__neg__')
def __nonzero__(self):
return self.obj_unaryfunc('__nonzero__')
def __pos__(self):
return self.unaryfunc('__pos__')
def __pow__(self, other, mod=None):
return self.ternaryfunc(other, mod, '__pow__')
def __radd__(self, other):
return self.binaryfunc(other, '__radd__')
def __rdiv__(self, other):
return self.binaryfunc(other, '__rdiv__')
def __rdivmod__(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.__rdivmod__(other_mpd)
q.dec, r.dec = self.dec.__rdivmod__(other_dec, context.d)
q.verify('__rdivmod__', (self, other))
r.verify('__rdivmod__', (self, other))
return (q, r)
# __reduce__
def __repr__(self):
self.obj_unaryfunc('__repr__')
return "cdec('" + str(self.mpd) + "')"
def __rfloordiv__(self, other):
return self.binaryfunc(other, '__rfloordiv__')
def __rmod__(self, other):
return self.binaryfunc(other, '__rmod__')
def __rmul__(self, other):
return self.binaryfunc(other, '__rmul__')
def __rsub__(self, other):
return self.binaryfunc(other, '__rsub__')
def __rtruediv__(self, other):
return self.binaryfunc(other, '__rtruediv__')
def __rpow__(self, other):
return other.__pow__(self)
def __str__(self):
self.obj_unaryfunc('__str__')
return str(self.mpd)
def __sub__(self, other):
return self.binaryfunc(other, '__sub__')
def __truediv__(self, other):
return self.binaryfunc(other, '__truediv__')
def __trunc__(self):
# ValueError or OverflowError
if self.mpd.is_special():
return (None, None)
return self.obj_unaryfunc('__trunc__')
def _apply(self):
return self.unaryfunc('_apply')
def abs(self):
return self.unaryfunc_ctx('abs')
def add(self, other):
return self.binaryfunc_ctx(other, 'add')
def adjusted(self):
return self.obj_unaryfunc('adjusted')
def canonical(self):
return self.unaryfunc('canonical')
def compare(self, other):
return self.binaryfunc(other, 'compare')
def compare_signal(self, other):
return self.binaryfunc(other, 'compare_signal')
def compare_total(self, other):
return self.binaryfunc(other, 'compare_total')
def compare_total_mag(self, other):
return self.binaryfunc(other, 'compare_total_mag')
def copy_abs(self):
return self.unaryfunc('copy_abs')
def copy_negate(self):
return self.unaryfunc('copy_negate')
def copy_sign(self, other):
return self.binaryfunc(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('is_canonical')
def is_finite(self):
return self.obj_unaryfunc('is_finite')
def is_infinite(self):
return self.obj_unaryfunc('is_infinite')
def is_nan(self):
return self.obj_unaryfunc('is_nan')
def is_normal(self):
return self.obj_unaryfunc('is_normal')
def is_qnan(self):
return self.obj_unaryfunc('is_qnan')
def is_signed(self):
return self.obj_unaryfunc('is_signed')
def is_snan(self):
return self.obj_unaryfunc('is_snan')
def is_subnormal(self):
return self.obj_unaryfunc('is_subnormal')
def is_zero(self):
return self.obj_unaryfunc('is_zero')
def ln(self):
return self.unaryfunc('ln')
def log10(self):
global PY25_DLOG10_HAVE_WARNED
try:
return self.unaryfunc('log10')
except NameError:
if not PY25_DLOG10_HAVE_WARNED:
sys.stderr.write(
"\n\n*** warning: detected known bug in decimal.py: "
"replace div_nearest with _div_nearest in _dlog10().\n\n\n")
PY25_DLOG10_HAVE_WARNED = 1
return None
def logb(self):
return self.unaryfunc('logb')
def logical_and(self, other):
return self.binaryfunc(other, 'logical_and')
def logical_invert(self):
return self.unaryfunc('logical_invert')
def logical_or(self, other):
return self.binaryfunc(other, 'logical_or')
def logical_xor(self, other):
return self.binaryfunc(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, third=None):
"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
c.mpd = pow(self.mpd, other_mpd, third_mpd)
c.dec = pow(self.dec, other_dec, third_dec)
c.verify('power', (self, other, third))
return c
# powmod: same as __pow__ or power with three arguments
def quantize(self, other):
return self.binaryfunc(other, 'quantize')
def radix(self):
return self.unaryfunc('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(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):
context.clear_status()
r_mpd = self.mpd.to_sci_string()
r_dec = context.d.to_sci_string(self.dec)
verify((r_mpd, r_dec), 'to_sci_string', (self,))
return r_mpd
def log(fmt, args=None):
if args:
sys.stdout.write(''.join((fmt, '\n')) % args)
else:
sys.stdout.write(''.join((str(fmt), '\n')))
sys.stdout.flush()
def test_method(method, testspecs, testfunc):
log("testing %s ...", method)
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 sorted(decround):
context.rounding = rounding
context.capitals = random.randrange(2)
if spec['clamp'] == 2:
context.clamp = random.randrange(2)
else:
context.clamp = spec['clamp']
exprange = context.f.Emax
testfunc(method, prec, exprange, restr_range, spec['iter'])
def test_unary(method, prec, exprange, restr_range, iter):
if method in ['__int__', '__long__', '__trunc__', 'to_integral',
'to_integral_value', 'to_integral_value']:
exprange = restr_range
if py_minor == 5 and method == '__hash__':
exprange = restr_range
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_tuple(prec, exprange, iter):
try:
x = cdec(a)
getattr(x, method)()
except CdecException, err:
log(err)
if py_minor >= 7:
for a in un_randfloat():
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
try:
s = randtuple(prec, exprange)
x = cdec(s)
getattr(x, method)()
except CdecException, err:
log(err)
except OverflowError:
pass
def test_un_logical(method, prec, exprange, restr_range, iter):
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, restr_range)
x = cdec(s)
getattr(x, method)()
except CdecException, err:
log(err)
except OverflowError:
pass
def test_binary(method, prec, exprange, restr_range, iter):
if method in ['__pow__', '__rpow__', 'power']:
exprange = restr_range
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)
if py_minor >= 7:
for a, b in bin_randfloat():
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, exprange, restr_range, iter):
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, restr_range)
s2 = randdec(prec, restr_range)
try:
x = cdec(s1)
y = cdec(s2)
getattr(x, method)(y)
except CdecException, err:
log(err)
def test_ternary(method, prec, exprange, restr_range, iter):
if method in ['__pow__', 'power']:
exprange = restr_range
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)
if py_minor >= 7:
for a, b, c in tern_randfloat():
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(method, prec, exprange, restr_range, iter):
for a in un_incr_digits_tuple(prec, restr_range, iter):
context.clear_status()
try:
fmt = rand_format(chr(random.randrange(32, 128)))
x = format(context.f.create_decimal(a), fmt)
y = format(context.d.create_decimal(a), fmt)
except Exception, err:
print err, fmt
continue
if x != y:
print context.f
print context.d
print "\n%s %s" % (a, fmt)
print "%s %s\n" % (x, y)
for i in range(1000):
context.clear_status()
try:
a = randdec(99, 9999)
fmt = rand_format(chr(random.randrange(32, 128)))
x = format(context.f.create_decimal(a), fmt)
y = format(context.d.create_decimal(a), fmt)
except Exception, err:
print err, fmt
continue
if x != y:
print context.f
print context.d
print "\n%s %s" % (a, fmt)
print "%s %s\n" % (x, y)
def test_locale(method, prec, exprange, restr_range, iter):
for a in un_incr_digits_tuple(prec, restr_range, iter):
context.clear_status()
try:
fmt = rand_locale()
x = format(context.f.create_decimal(a), fmt)
y = format(context.d.create_decimal(a), fmt)
except Exception, err:
print err, fmt
continue
if x != y:
print context.f
print context.d
print locale.getlocale(locale.LC_NUMERIC)
print "%s %s" % (a, fmt)
print array.array('b', x)
print array.array('b', y)
for i in range(1000):
context.clear_status()
try:
a = randdec(99, 9999)
fmt = rand_locale()
x = format(context.f.create_decimal(a), fmt)
y = format(context.d.create_decimal(a), fmt)
except Exception, err:
print err, fmt
continue
if x != y:
print context.f
print context.d
print locale.getlocale(locale.LC_NUMERIC)
print "%s %s" % (a, fmt)
print array.array('b', x)
print array.array('b', y)
def test_from_float(method, prec, exprange, restr_range, iter):
exprange = 384
for i in range(1000):
context.clear_status()
intpart = str(random.randrange(100000000000000000000000000000000000000))
fracpart = str(random.randrange(100000000000000000000000000000000000000))
exp = str(random.randrange(-384, 384))
fstring = intpart + '.' + fracpart + 'e' + exp
f = float(fstring)
try:
c = cdec(f)
except CdecException, err:
log(err)
def assert_eq_status(c, d):
"""assert equality of cdecimal and decimal status"""
for signal in c.flags:
if signal == cdecimal.FloatOperation:
continue
if c.flags[signal] == (not d.flags[deccond[signal]]):
return False
return True
def test_quantize_api(method, prec, exprange, restr_range, iter):
for a in un_incr_digits(prec, restr_range, 1):
emax = random.randrange(exprange)
emin = random.randrange(-exprange, 0)
clamp = random.randrange(2)
exp = randdec(2*prec, exprange)
for rounding in sorted(decround):
try:
c = cdecimal.Context(prec=prec, Emax=emax, Emin=emin, clamp=clamp, traps=[])
d = decimal.Context(prec=prec, Emax=emax, Emin=emin, _clamp=clamp, traps=[])
x = cdecimal.Decimal(a)
y = cdecimal.Decimal(exp)
cresult = x.quantize(y, rounding, c)
u = decimal.Decimal(a)
v = decimal.Decimal(exp)
dresult = u.quantize(v, decround[rounding], d)
except Exception, err:
print(err)
continue
if str(cresult) != str(dresult) or \
not assert_eq_status(c, d):
print("%s\n%s\n" % (c, d))
print("x: %s\ny: %s\nu: %s\nv: %s\n" % (x, y, u, v))
print("a: %s exp: %s\n" % (a, exp))
print("cresult: %s\ndresult: %s\n" % (cresult, dresult))
for i in range(1000):
a = randdec(prec, 9999)
prec = random.randrange(1, 50)
emax = random.randrange(exprange)
emin = random.randrange(-exprange, 0)
clamp = random.randrange(2)
exp = randdec(2*prec, exprange)
for rounding in sorted(decround):
try:
c = cdecimal.Context(prec=prec, Emax=emax, Emin=emin, clamp=clamp, traps=[])
d = decimal.Context(prec=prec, Emax=emax, Emin=emin, _clamp=clamp, traps=[])
x = cdecimal.Decimal(a)
y = cdecimal.Decimal(exp)
cresult = x.quantize(context=c, exp=y, rounding=rounding)
u = decimal.Decimal(a)
v = decimal.Decimal(exp)
dresult = u.quantize(context=d, exp=v, rounding=decround[rounding])
except Exception, err:
print(err)
continue
if str(cresult) != str(dresult) or \
not assert_eq_status(c, d):
print("%s\n%s\n" % (c, d))
print("x: %s\ny: %s\nu: %s\nv: %s\n" % (x, y, u, v))
print("a: %s exp: %s\n" % (a, exp))
print("cresult: %s\ndresult: %s\n" % (cresult, dresult))
if __name__ == '__main__':
import time
randseed = int(time.time())
random.seed(randseed)
base_expts = [(cdecimal.MIN_EMIN, cdecimal.MAX_EMAX)]
if cdecimal.MAX_EMAX == 999999999999999999:
base_expts.append((-999999999, 999999999))
base = {
'name': 'base',
'expts': base_expts,
'prec': [],
'clamp': 2,
'iter': None,
'samples': None,
}
small = {
'name': 'small',
'prec': [1, 2, 3, 4, 5],
'expts': [(-1,1), (-2,2), (-3,3), (-4,4), (-5,5)],
'clamp': 2,
'iter': None
}
ieee = [
{'name': 'decimal32', 'prec': [7], 'expts': [(-95, 96)], 'clamp': 1, 'iter': None},
{'name': 'decimal64', 'prec': [16], 'expts': [(-383, 384)], 'clamp': 1, 'iter': None},
{'name': 'decimal128', 'prec': [34], 'expts': [(-6143, 6144)], 'clamp': 1, 'iter': None}
]
if '--medium' in sys.argv:
base['expts'].append(('rand', 'rand'))
base['samples'] = None
testspecs = [small] + ieee + [base]
if '--long' in sys.argv:
base['expts'].append(('rand', 'rand'))
base['samples'] = 5
testspecs = [small] + ieee + [base]
elif '--all' in sys.argv:
base['expts'].append(('rand', 'rand'))
base['samples'] = 100
testspecs = [small] + ieee + [base]
else: # --short
rand_ieee = random.choice(ieee)
base['iter'] = small['iter'] = rand_ieee['iter'] = 1
base['samples'] = 1
base['expts'] = [random.choice(base_expts)]
prec = random.randrange(1, 6)
small['prec'] = [prec]
small['expts'] = [(-prec, prec)]
testspecs = [small, rand_ieee, base]
all_decimal_methods = set(dir(cdecimal.Decimal) + dir(decimal.Decimal))
all_cdec_methods = [m for m in dir(cdec) if m in all_decimal_methods]
untested_methods = [m for m in all_decimal_methods if not (m in all_cdec_methods)]
unary_methods = []
binary_methods = []
ternary_methods = []
for m in all_cdec_methods:
try:
l = len(inspect.getargspec(getattr(cdec, m))[0])
except TypeError:
continue
if l == 1:
unary_methods.append(m)
elif l == 2:
binary_methods.append(m)
elif l == 3:
ternary_methods.append(m)
else:
raise ValueError((m, l))
unary_methods.append('__deepcopy__')
binary_methods.remove('__deepcopy__')
binary_methods.remove('__new__')
binary_methods.append('power')
untested_methods.remove('from_float')
if py_minor < 6:
unary_methods.remove('__trunc__')
for elem in ['__ge__', '__gt__', '__le__', '__lt__']:
binary_methods.remove(elem)
untested_methods.sort()
unary_methods.sort()
binary_methods.sort()
ternary_methods.sort()
log("\nRandom seed: %d\n\n", randseed)
log("Skipping tests: \n\n%s\n", untested_methods)
for method in unary_methods:
test_method(method, testspecs, test_unary)
for method in binary_methods:
test_method(method, testspecs, test_binary)
for method in ternary_methods:
test_method(method, testspecs, test_ternary)
test_method('logical_invert', testspecs, test_un_logical)
for method in ['logical_and', 'logical_or', 'logical_xor']:
test_method(method, testspecs, test_bin_logical)
test_method('quantize_api', testspecs, test_quantize_api)
if HAVE_FORMATHELPER and py_minor >= 7:
# Some tests will fail with 2.6, since alignment has been changed
# in decimal.py 2.7.
test_method('format', testspecs, test_format)
test_method('locale', testspecs, test_locale)
test_method('from_float', testspecs, test_from_float)
sys.exit(EXIT_STATUS)