General clean-up. Lot of margin corrections, comments, some typos.

Exceptions now are raised in the new style. And a mockup class is
now also new style. Thanks Santiago Pereson.
This commit is contained in:
Facundo Batista 2007-04-10 12:58:45 +00:00
parent 8341aac123
commit 59c5884b4c

View file

@ -29,8 +29,8 @@ and IEEE standard 854-1987:
Decimal floating point has finite precision with arbitrarily large bounds. Decimal floating point has finite precision with arbitrarily large bounds.
The purpose of the module is to support arithmetic using familiar The purpose of this module is to support arithmetic using familiar
"schoolhouse" rules and to avoid the some of tricky representation "schoolhouse" rules and to avoid some of the tricky representation
issues associated with binary floating point. The package is especially issues associated with binary floating point. The package is especially
useful for financial applications or for contexts where users have useful for financial applications or for contexts where users have
expectations that are at odds with binary floating point (for instance, expectations that are at odds with binary floating point (for instance,
@ -379,7 +379,7 @@ _condition_map = {ConversionSyntax:InvalidOperation,
DivisionUndefined:InvalidOperation, DivisionUndefined:InvalidOperation,
InvalidContext:InvalidOperation} InvalidContext:InvalidOperation}
##### Context Functions ####################################### ##### Context Functions ##################################################
# The getcontext() and setcontext() function manage access to a thread-local # The getcontext() and setcontext() function manage access to a thread-local
# current context. Py2.4 offers direct support for thread locals. If that # current context. Py2.4 offers direct support for thread locals. If that
@ -392,7 +392,7 @@ try:
except ImportError: except ImportError:
# Python was compiled without threads; create a mock object instead # Python was compiled without threads; create a mock object instead
import sys import sys
class MockThreading: class MockThreading(object):
def local(self, sys=sys): def local(self, sys=sys):
return sys.modules[__name__] return sys.modules[__name__]
threading = MockThreading() threading = MockThreading()
@ -502,7 +502,7 @@ def localcontext(ctx=None):
return _ContextManager(ctx) return _ContextManager(ctx)
##### Decimal class ########################################### ##### Decimal class #######################################################
class Decimal(object): class Decimal(object):
"""Floating point class for decimal arithmetic.""" """Floating point class for decimal arithmetic."""
@ -518,7 +518,7 @@ class Decimal(object):
>>> Decimal('3.14') # string input >>> Decimal('3.14') # string input
Decimal("3.14") Decimal("3.14")
>>> Decimal((0, (3, 1, 4), -2)) # tuple input (sign, digit_tuple, exponent) >>> Decimal((0, (3, 1, 4), -2)) # tuple (sign, digit_tuple, exponent)
Decimal("3.14") Decimal("3.14")
>>> Decimal(314) # int or long >>> Decimal(314) # int or long
Decimal("314") Decimal("314")
@ -557,13 +557,13 @@ class Decimal(object):
# tuple/list conversion (possibly from as_tuple()) # tuple/list conversion (possibly from as_tuple())
if isinstance(value, (list,tuple)): if isinstance(value, (list,tuple)):
if len(value) != 3: if len(value) != 3:
raise ValueError, 'Invalid arguments' raise ValueError('Invalid arguments')
if value[0] not in (0,1): if value[0] not in (0,1):
raise ValueError, 'Invalid sign' raise ValueError('Invalid sign')
for digit in value[1]: for digit in value[1]:
if not isinstance(digit, (int,long)) or digit < 0: if not isinstance(digit, (int,long)) or digit < 0:
raise ValueError, "The second value in the tuple must be composed of non negative integer elements." raise ValueError("The second value in the tuple must be"
"composed of non negative integer elements.")
self._sign = value[0] self._sign = value[0]
self._int = tuple(value[1]) self._int = tuple(value[1])
if value[2] in ('F','n','N'): if value[2] in ('F','n','N'):
@ -611,7 +611,8 @@ class Decimal(object):
self._sign, self._int, self._exp = _string2exact(value) self._sign, self._int, self._exp = _string2exact(value)
except ValueError: except ValueError:
self._is_special = True self._is_special = True
self._sign, self._int, self._exp = context._raise_error(ConversionSyntax) self._sign, self._int, self._exp = \
context._raise_error(ConversionSyntax)
return self return self
raise TypeError("Cannot convert %r to Decimal" % value) raise TypeError("Cannot convert %r to Decimal" % value)
@ -762,7 +763,7 @@ class Decimal(object):
if other is NotImplemented: if other is NotImplemented:
return other return other
#compare(NaN, NaN) = NaN # Compare(NaN, NaN) = NaN
if (self._is_special or other and other._is_special): if (self._is_special or other and other._is_special):
ans = self._check_nans(other, context) ans = self._check_nans(other, context)
if ans: if ans:
@ -1096,7 +1097,8 @@ class Decimal(object):
if ans: if ans:
return ans return ans
return Decimal(self) # Must be infinite, and incrementing makes no difference # Must be infinite, and incrementing makes no difference
return Decimal(self)
L = list(self._int) L = list(self._int)
L[-1] += 1 L[-1] += 1
@ -1265,7 +1267,6 @@ class Decimal(object):
return context._raise_error(DivisionByZero, 'x / 0', sign) return context._raise_error(DivisionByZero, 'x / 0', sign)
# OK, so neither = 0, INF or NaN # OK, so neither = 0, INF or NaN
shouldround = context._rounding_decision == ALWAYS_ROUND shouldround = context._rounding_decision == ALWAYS_ROUND
# If we're dividing into ints, and self < other, stop. # If we're dividing into ints, and self < other, stop.
@ -1416,7 +1417,7 @@ class Decimal(object):
# ignored in the calling function. # ignored in the calling function.
context = context._shallow_copy() context = context._shallow_copy()
flags = context._ignore_flags(Rounded, Inexact) flags = context._ignore_flags(Rounded, Inexact)
#keep DivisionImpossible flags # Keep DivisionImpossible flags
(side, r) = self.__divmod__(other, context=context) (side, r) = self.__divmod__(other, context=context)
if r._isnan(): if r._isnan():
@ -1461,7 +1462,8 @@ class Decimal(object):
if r > comparison or decrease and r == comparison: if r > comparison or decrease and r == comparison:
r._sign, comparison._sign = s1, s2 r._sign, comparison._sign = s1, s2
context.prec += 1 context.prec += 1
if len(side.__add__(Decimal(1), context=context)._int) >= context.prec: numbsquant = len(side.__add__(Decimal(1), context=context)._int)
if numbsquant >= context.prec:
context.prec -= 1 context.prec -= 1
return context._raise_error(DivisionImpossible)[1] return context._raise_error(DivisionImpossible)[1]
context.prec -= 1 context.prec -= 1
@ -1496,7 +1498,7 @@ class Decimal(object):
context = getcontext() context = getcontext()
return context._raise_error(InvalidContext) return context._raise_error(InvalidContext)
elif self._isinfinity(): elif self._isinfinity():
raise OverflowError, "Cannot convert infinity to long" raise OverflowError("Cannot convert infinity to long")
if self._exp >= 0: if self._exp >= 0:
s = ''.join(map(str, self._int)) + '0'*self._exp s = ''.join(map(str, self._int)) + '0'*self._exp
else: else:
@ -1573,7 +1575,8 @@ class Decimal(object):
return ans return ans
context._raise_error(Inexact) context._raise_error(Inexact)
context._raise_error(Rounded) context._raise_error(Rounded)
return context._raise_error(Overflow, 'above Emax', ans._sign) c = context._raise_error(Overflow, 'above Emax', ans._sign)
return c
return ans return ans
def _round(self, prec=None, rounding=None, context=None): def _round(self, prec=None, rounding=None, context=None):
@ -1770,9 +1773,10 @@ class Decimal(object):
return Infsign[sign] return Infsign[sign]
return Decimal( (sign, (0,), 0) ) return Decimal( (sign, (0,), 0) )
#with ludicrously large exponent, just raise an overflow and return inf. # With ludicrously large exponent, just raise an overflow
if not modulo and n > 0 and (self._exp + len(self._int) - 1) * n > context.Emax \ # and return inf.
and self: if not modulo and n > 0 and \
(self._exp + len(self._int) - 1) * n > context.Emax and self:
tmp = Decimal('inf') tmp = Decimal('inf')
tmp._sign = sign tmp._sign = sign
@ -1801,7 +1805,7 @@ class Decimal(object):
spot <<= 1 spot <<= 1
spot >>= 1 spot >>= 1
#Spot is the highest power of 2 less than n # spot is the highest power of 2 less than n
while spot: while spot:
val = val.__mul__(val, context=context) val = val.__mul__(val, context=context)
if val._isinfinity(): if val._isinfinity():
@ -2005,7 +2009,6 @@ class Decimal(object):
ans._exp -= 1 + tmp.adjusted() // 2 ans._exp -= 1 + tmp.adjusted() // 2
# ans is now a linear approximation. # ans is now a linear approximation.
Emax, Emin = context.Emax, context.Emin Emax, Emin = context.Emax, context.Emin
context.Emax, context.Emin = DefaultContext.Emax, DefaultContext.Emin context.Emax, context.Emin = DefaultContext.Emax, DefaultContext.Emin
@ -2020,7 +2023,7 @@ class Decimal(object):
if context.prec == maxp: if context.prec == maxp:
break break
#round to the answer's precision-- the only error can be 1 ulp. # Round to the answer's precision-- the only error can be 1 ulp.
context.prec = firstprec context.prec = firstprec
prevexp = ans.adjusted() prevexp = ans.adjusted()
ans = ans._round(context=context) ans = ans._round(context=context)
@ -2081,7 +2084,7 @@ class Decimal(object):
return other return other
if self._is_special or other._is_special: if self._is_special or other._is_special:
# if one operand is a quiet NaN and the other is number, then the # If one operand is a quiet NaN and the other is number, then the
# number is always returned # number is always returned
sn = self._isnan() sn = self._isnan()
on = other._isnan() on = other._isnan()
@ -2095,13 +2098,13 @@ class Decimal(object):
ans = self ans = self
c = self.__cmp__(other) c = self.__cmp__(other)
if c == 0: if c == 0:
# if both operands are finite and equal in numerical value # If both operands are finite and equal in numerical value
# then an ordering is applied: # then an ordering is applied:
# #
# if the signs differ then max returns the operand with the # If the signs differ then max returns the operand with the
# positive sign and min returns the operand with the negative sign # positive sign and min returns the operand with the negative sign
# #
# if the signs are the same then the exponent is used to select # If the signs are the same then the exponent is used to select
# the result. # the result.
if self._sign != other._sign: if self._sign != other._sign:
if self._sign: if self._sign:
@ -2122,7 +2125,7 @@ class Decimal(object):
def min(self, other, context=None): def min(self, other, context=None):
"""Returns the smaller value. """Returns the smaller value.
like min(self, other) except if one is not a number, returns Like min(self, other) except if one is not a number, returns
NaN (and signals if one is sNaN). Also rounds. NaN (and signals if one is sNaN). Also rounds.
""" """
other = _convert_other(other) other = _convert_other(other)
@ -2130,7 +2133,7 @@ class Decimal(object):
return other return other
if self._is_special or other._is_special: if self._is_special or other._is_special:
# if one operand is a quiet NaN and the other is number, then the # If one operand is a quiet NaN and the other is number, then the
# number is always returned # number is always returned
sn = self._isnan() sn = self._isnan()
on = other._isnan() on = other._isnan()
@ -2144,13 +2147,13 @@ class Decimal(object):
ans = self ans = self
c = self.__cmp__(other) c = self.__cmp__(other)
if c == 0: if c == 0:
# if both operands are finite and equal in numerical value # If both operands are finite and equal in numerical value
# then an ordering is applied: # then an ordering is applied:
# #
# if the signs differ then max returns the operand with the # If the signs differ then max returns the operand with the
# positive sign and min returns the operand with the negative sign # positive sign and min returns the operand with the negative sign
# #
# if the signs are the same then the exponent is used to select # If the signs are the same then the exponent is used to select
# the result. # the result.
if self._sign != other._sign: if self._sign != other._sign:
if other._sign: if other._sign:
@ -2189,7 +2192,7 @@ class Decimal(object):
except TypeError: except TypeError:
return 0 return 0
# support for pickling, copy, and deepcopy # Support for pickling, copy, and deepcopy
def __reduce__(self): def __reduce__(self):
return (self.__class__, (str(self),)) return (self.__class__, (str(self),))
@ -2203,11 +2206,12 @@ class Decimal(object):
return self # My components are also immutable return self # My components are also immutable
return self.__class__(str(self)) return self.__class__(str(self))
##### Context class ########################################### ##### Context class #######################################################
# get rounding method function: # get rounding method function:
rounding_functions = [name for name in Decimal.__dict__.keys() if name.startswith('_round_')] rounding_functions = [name for name in Decimal.__dict__.keys()
if name.startswith('_round_')]
for name in rounding_functions: for name in rounding_functions:
# name is like _round_half_even, goes to the global ROUND_HALF_EVEN value. # name is like _round_half_even, goes to the global ROUND_HALF_EVEN value.
globalname = name[1:].upper() globalname = name[1:].upper()
@ -2236,7 +2240,7 @@ class Context(object):
Contains: Contains:
prec - precision (for use in rounding, division, square roots..) prec - precision (for use in rounding, division, square roots..)
rounding - rounding type. (how you round) rounding - rounding type (how you round)
_rounding_decision - ALWAYS_ROUND, NEVER_ROUND -- do you round? _rounding_decision - ALWAYS_ROUND, NEVER_ROUND -- do you round?
traps - If traps[exception] = 1, then the exception is traps - If traps[exception] = 1, then the exception is
raised when it is caused. Otherwise, a value is raised when it is caused. Otherwise, a value is
@ -2277,9 +2281,13 @@ class Context(object):
def __repr__(self): def __repr__(self):
"""Show the current context.""" """Show the current context."""
s = [] s = []
s.append('Context(prec=%(prec)d, rounding=%(rounding)s, Emin=%(Emin)d, Emax=%(Emax)d, capitals=%(capitals)d' % vars(self)) s.append('Context(prec=%(prec)d, rounding=%(rounding)s, '
s.append('flags=[' + ', '.join([f.__name__ for f, v in self.flags.items() if v]) + ']') 'Emin=%(Emin)d, Emax=%(Emax)d, capitals=%(capitals)d'
s.append('traps=[' + ', '.join([t.__name__ for t, v in self.traps.items() if v]) + ']') % vars(self))
names = [f.__name__ for f, v in self.flags.items() if v]
s.append('flags=[' + ', '.join(names) + ']')
names = [t.__name__ for t, v in self.traps.items() if v]
s.append('traps=[' + ', '.join(names) + ']')
return ', '.join(s) + ')' return ', '.join(s) + ')'
def clear_flags(self): def clear_flags(self):
@ -2296,9 +2304,9 @@ class Context(object):
def copy(self): def copy(self):
"""Returns a deep copy from self.""" """Returns a deep copy from self."""
nc = Context(self.prec, self.rounding, self.traps.copy(), self.flags.copy(), nc = Context(self.prec, self.rounding, self.traps.copy(),
self._rounding_decision, self.Emin, self.Emax, self.flags.copy(), self._rounding_decision, self.Emin,
self.capitals, self._clamp, self._ignored_flags) self.Emax, self.capitals, self._clamp, self._ignored_flags)
return nc return nc
__copy__ = copy __copy__ = copy
@ -2345,7 +2353,7 @@ class Context(object):
def __hash__(self): def __hash__(self):
"""A Context cannot be hashed.""" """A Context cannot be hashed."""
# We inherit object.__hash__, so we must deny this explicitly # We inherit object.__hash__, so we must deny this explicitly
raise TypeError, "Cannot hash a Context." raise TypeError("Cannot hash a Context.")
def Etiny(self): def Etiny(self):
"""Returns Etiny (= Emin - prec + 1)""" """Returns Etiny (= Emin - prec + 1)"""
@ -2623,8 +2631,8 @@ class Context(object):
1) before use. 1) before use.
If the increased precision needed for the intermediate calculations If the increased precision needed for the intermediate calculations
exceeds the capabilities of the implementation then an Invalid operation exceeds the capabilities of the implementation then an Invalid
condition is raised. operation condition is raised.
If, when raising to a negative power, an underflow occurs during the If, when raising to a negative power, an underflow occurs during the
division into 1, the operation is not halted at that point but division into 1, the operation is not halted at that point but
@ -2662,7 +2670,7 @@ class Context(object):
return a.__pow__(b, modulo, context=self) return a.__pow__(b, modulo, context=self)
def quantize(self, a, b): def quantize(self, a, b):
"""Returns a value equal to 'a' (rounded) and having the exponent of 'b'. """Returns a value equal to 'a' (rounded), having the exponent of 'b'.
The coefficient of the result is derived from that of the left-hand The coefficient of the result is derived from that of the left-hand
operand. It may be rounded using the current rounding setting (if the operand. It may be rounded using the current rounding setting (if the
@ -2672,8 +2680,8 @@ class Context(object):
Unlike other operations, if the length of the coefficient after the Unlike other operations, if the length of the coefficient after the
quantize operation would be greater than precision then an Invalid quantize operation would be greater than precision then an Invalid
operation condition is raised. This guarantees that, unless there is an operation condition is raised. This guarantees that, unless there is
error condition, the exponent of the result of a quantize is always an error condition, the exponent of the result of a quantize is always
equal to that of the right-hand operand. equal to that of the right-hand operand.
Also unlike other operations, quantize will never raise Underflow, even Also unlike other operations, quantize will never raise Underflow, even
@ -2716,9 +2724,9 @@ class Context(object):
"""Returns the remainder from integer division. """Returns the remainder from integer division.
The result is the residue of the dividend after the operation of The result is the residue of the dividend after the operation of
calculating integer division as described for divide-integer, rounded to calculating integer division as described for divide-integer, rounded
precision digits if necessary. The sign of the result, if non-zero, is to precision digits if necessary. The sign of the result, if
the same as that of the original dividend. non-zero, is the same as that of the original dividend.
This operation will fail under the same conditions as integer division This operation will fail under the same conditions as integer division
(that is, if integer division on the same two operands would fail, the (that is, if integer division on the same two operands would fail, the
@ -2784,7 +2792,7 @@ class Context(object):
return a.same_quantum(b) return a.same_quantum(b)
def sqrt(self, a): def sqrt(self, a):
"""Returns the square root of a non-negative number to context precision. """Square root of a non-negative number to context precision.
If the result must be inexact, it is rounded using the round-half-even If the result must be inexact, it is rounded using the round-half-even
algorithm. algorithm.
@ -2920,8 +2928,9 @@ def _normalize(op1, op2, shouldround = 0, prec = 0):
other_len = len(str(other.int)) other_len = len(str(other.int))
if numdigits > (other_len + prec + 1 - tmp_len): if numdigits > (other_len + prec + 1 - tmp_len):
# If the difference in adjusted exps is > prec+1, we know # If the difference in adjusted exps is > prec+1, we know
# other is insignificant, so might as well put a 1 after the precision. # other is insignificant, so might as well put a 1 after the
# (since this is only for addition.) Also stops use of massive longs. # precision (since this is only for addition). Also stops
# use of massive longs.
extend = prec + 2 - tmp_len extend = prec + 2 - tmp_len
if extend <= 0: if extend <= 0:
@ -2958,7 +2967,7 @@ def _adjust_coefficients(op1, op2):
return op1, op2, adjust return op1, op2, adjust
##### Helper Functions ######################################## ##### Helper Functions ####################################################
def _convert_other(other): def _convert_other(other):
"""Convert other to Decimal. """Convert other to Decimal.
@ -2999,7 +3008,7 @@ def _isnan(num):
if not num: if not num:
return 0 return 0
#get the sign, get rid of trailing [+-] # Get the sign, get rid of trailing [+-]
sign = 0 sign = 0
if num[0] == '+': if num[0] == '+':
num = num[1:] num = num[1:]
@ -3018,7 +3027,7 @@ def _isnan(num):
return 0 return 0
##### Setup Specific Contexts ################################ ##### Setup Specific Contexts ############################################
# The default context prototype used by Context() # The default context prototype used by Context()
# Is mutable, so that new contexts can have different default values # Is mutable, so that new contexts can have different default values
@ -3051,7 +3060,7 @@ ExtendedContext = Context(
) )
##### Useful Constants (internal use only) #################### ##### Useful Constants (internal use only) ################################
# Reusable defaults # Reusable defaults
Inf = Decimal('Inf') Inf = Decimal('Inf')
@ -3063,7 +3072,7 @@ Infsign = (Inf, negInf)
NaN = Decimal('NaN') NaN = Decimal('NaN')
##### crud for parsing strings ################################# ##### crud for parsing strings #############################################
import re import re
# There's an optional sign at the start, and an optional exponent # There's an optional sign at the start, and an optional exponent
@ -3087,9 +3096,11 @@ _parser = re.compile(r"""
del re del re
# return sign, n, p s.t. float string value == -1**sign * n * 10**p exactly
def _string2exact(s): def _string2exact(s):
"""Return sign, n, p s.t.
Float string value == -1**sign * n * 10**p exactly
"""
m = _parser(s) m = _parser(s)
if m is None: if m is None:
raise ValueError("invalid literal for Decimal: %r" % s) raise ValueError("invalid literal for Decimal: %r" % s)