Comments and docs cleanups, and some little fixes, provided by Santiágo Peresón

This commit is contained in:
Facundo Batista 2006-07-18 12:16:13 +00:00
parent caebe22038
commit ac4ae4baf7

View file

@ -29,8 +29,8 @@ and IEEE standard 854-1987:
Decimal floating point has finite precision with arbitrarily large bounds.
The purpose of the module is to support arithmetic using familiar
"schoolhouse" rules and to avoid the some of tricky representation
The purpose of this module is to support arithmetic using familiar
"schoolhouse" rules and to avoid some of the tricky representation
issues associated with binary floating point. The package is especially
useful for financial applications or for contexts where users have
expectations that are at odds with binary floating point (for instance,
@ -160,17 +160,17 @@ class DecimalException(ArithmeticError):
called if the others are present. This isn't actually used for
anything, though.
handle -- Called when context._raise_error is called and the
trap_enabler is set. First argument is self, second is the
context. More arguments can be given, those being after
the explanation in _raise_error (For example,
context._raise_error(NewError, '(-x)!', self._sign) would
call NewError().handle(context, self._sign).)
To define a new exception, it should be sufficient to have it derive
from DecimalException.
"""
def handle(self, context, *args):
"""Called when context._raise_error is called and trap_enabler is set.
First argument is self, second is the context. More arguments can
be given, those being after the explanation in _raise_error (For
example, context._raise_error(NewError, '(-x)!', self._sign) would
call NewError().handle(context, self._sign).)
"""
pass
@ -185,6 +185,7 @@ class Clamped(DecimalException):
this latter case, the exponent is reduced to fit and the corresponding
number of zero digits are appended to the coefficient ("fold-down").
"""
pass
class InvalidOperation(DecimalException):
@ -211,6 +212,7 @@ class InvalidOperation(DecimalException):
return Decimal( (args[1]._sign, args[1]._int, 'n') )
return NaN
class ConversionSyntax(InvalidOperation):
"""Trying to convert badly formed string.
@ -218,10 +220,10 @@ class ConversionSyntax(InvalidOperation):
converted to a number and it does not conform to the numeric string
syntax. The result is [0,qNaN].
"""
def handle(self, context, *args):
return (0, (0,), 'n') # Passed to something which uses a tuple.
class DivisionByZero(DecimalException, ZeroDivisionError):
"""Division by 0.
@ -234,12 +236,12 @@ class DivisionByZero(DecimalException, ZeroDivisionError):
or of the signs of the operands for divide, or is 1 for an odd power of
-0, for power.
"""
def handle(self, context, sign, double = None, *args):
if double is not None:
return (Infsign[sign],)*2
return Infsign[sign]
class DivisionImpossible(InvalidOperation):
"""Cannot perform the division adequately.
@ -247,10 +249,10 @@ class DivisionImpossible(InvalidOperation):
divide-integer or remainder operation had too many digits (would be
longer than precision). The result is [0,qNaN].
"""
def handle(self, context, *args):
return (NaN, NaN)
class DivisionUndefined(InvalidOperation, ZeroDivisionError):
"""Undefined result of division.
@ -258,12 +260,12 @@ class DivisionUndefined(InvalidOperation, ZeroDivisionError):
attempted (during a divide-integer, divide, or remainder operation), and
the dividend is also zero. The result is [0,qNaN].
"""
def handle(self, context, tup=None, *args):
if tup is not None:
return (NaN, NaN) #for 0 %0, 0 // 0
return (NaN, NaN) # For 0 %0, 0 // 0
return NaN
class Inexact(DecimalException):
"""Had to round, losing information.
@ -277,6 +279,7 @@ class Inexact(DecimalException):
"""
pass
class InvalidContext(InvalidOperation):
"""Invalid context. Unknown rounding, for example.
@ -287,10 +290,10 @@ class InvalidContext(InvalidOperation):
was specified. These aspects of the context need only be checked when
the values are required to be used. The result is [0,qNaN].
"""
def handle(self, context, *args):
return NaN
class Rounded(DecimalException):
"""Number got rounded (not necessarily changed during rounding).
@ -304,6 +307,7 @@ class Rounded(DecimalException):
"""
pass
class Subnormal(DecimalException):
"""Exponent < Emin before rounding.
@ -316,6 +320,7 @@ class Subnormal(DecimalException):
"""
pass
class Overflow(Inexact, Rounded):
"""Numerical overflow.
@ -337,7 +342,6 @@ class Overflow(Inexact, Rounded):
result is 0, or is [1,inf] otherwise. In all cases, Inexact and Rounded
will also be raised.
"""
def handle(self, context, sign, *args):
if context.rounding in (ROUND_HALF_UP, ROUND_HALF_EVEN,
ROUND_HALF_DOWN, ROUND_UP):
@ -368,6 +372,8 @@ class Underflow(Inexact, Rounded, Subnormal):
In all cases, Inexact, Rounded, and Subnormal will also be raised.
"""
pass
# List of public traps and flags
_signals = [Clamped, DivisionByZero, Inexact, Overflow, Rounded,
@ -379,25 +385,27 @@ _condition_map = {ConversionSyntax:InvalidOperation,
DivisionUndefined:InvalidOperation,
InvalidContext:InvalidOperation}
##### Context Functions #######################################
##### Context Functions #####################################################
# The getcontext() and setcontext() function manage access to a thread-local
# current context. Py2.4 offers direct support for thread locals. If that
# is not available, use threading.currentThread() which is slower but will
# work for older Pythons. If threads are not part of the build, create a
# mock threading object with threading.local() returning the module namespace.
# mock threading object with threading.local() returning the module
# namespace.
try:
import threading
except ImportError:
# Python was compiled without threads; create a mock object instead
import sys
class MockThreading:
class MockThreading(object):
def local(self, sys=sys):
return sys.modules[__name__]
threading = MockThreading()
del sys, MockThreading
try:
threading.local
@ -459,7 +467,7 @@ else:
del threading, local # Don't contaminate the namespace
##### Decimal class ###########################################
##### Decimal class ##########################################################
class Decimal(object):
"""Floating point class for decimal arithmetic."""
@ -475,7 +483,7 @@ class Decimal(object):
>>> Decimal('3.14') # string input
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(314) # int or long
Decimal("314")
@ -514,12 +522,13 @@ class Decimal(object):
# tuple/list conversion (possibly from as_tuple())
if isinstance(value, (list,tuple)):
if len(value) != 3:
raise ValueError, 'Invalid arguments'
raise ValueError('Invalid arguments')
if value[0] not in (0,1):
raise ValueError, 'Invalid sign'
raise ValueError('Invalid sign')
for digit in value[1]:
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._int = tuple(value[1])
@ -568,7 +577,8 @@ class Decimal(object):
self._sign, self._int, self._exp = _string2exact(value)
except ValueError:
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
raise TypeError("Cannot convert %r to Decimal" % value)
@ -670,7 +680,7 @@ class Decimal(object):
if self_adjusted == other_adjusted and \
self._int + (0,)*(self._exp - other._exp) == \
other._int + (0,)*(other._exp - self._exp):
return 0 #equal, except in precision. ([0]*(-x) = [])
return 0 # Equal, except in precision. ([0]*(-x) = [])
elif self_adjusted > other_adjusted and self._int[0] != 0:
return (-1)**self._sign
elif self_adjusted < other_adjusted and other._int[0] != 0:
@ -681,7 +691,7 @@ class Decimal(object):
context = getcontext()
context = context._shallow_copy()
rounding = context._set_rounding(ROUND_UP) #round away from 0
rounding = context._set_rounding(ROUND_UP) # Round away from 0
flags = context._ignore_all_flags()
res = self.__sub__(other, context=context)
@ -719,7 +729,7 @@ class Decimal(object):
if other is NotImplemented:
return other
#compare(NaN, NaN) = NaN
# Compare(NaN, NaN) = NaN
if (self._is_special or other and other._is_special):
ans = self._check_nans(other, context)
if ans:
@ -1052,8 +1062,8 @@ class Decimal(object):
ans = self._check_nans(context=context)
if 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[-1] += 1
@ -1222,7 +1232,6 @@ class Decimal(object):
return context._raise_error(DivisionByZero, 'x / 0', sign)
# OK, so neither = 0, INF or NaN
shouldround = context._rounding_decision == ALWAYS_ROUND
# If we're dividing into ints, and self < other, stop.
@ -1373,7 +1382,7 @@ class Decimal(object):
# ignored in the calling function.
context = context._shallow_copy()
flags = context._ignore_flags(Rounded, Inexact)
#keep DivisionImpossible flags
# Keep DivisionImpossible flags
(side, r) = self.__divmod__(other, context=context)
if r._isnan():
@ -1418,7 +1427,8 @@ class Decimal(object):
if r > comparison or decrease and r == comparison:
r._sign, comparison._sign = s1, s2
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
return context._raise_error(DivisionImpossible)[1]
context.prec -= 1
@ -1453,7 +1463,7 @@ class Decimal(object):
context = getcontext()
return context._raise_error(InvalidContext)
elif self._isinfinity():
raise OverflowError, "Cannot convert infinity to long"
raise OverflowError("Cannot convert infinity to long")
if self._exp >= 0:
s = ''.join(map(str, self._int)) + '0'*self._exp
else:
@ -1530,7 +1540,8 @@ class Decimal(object):
return ans
context._raise_error(Inexact)
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
def _round(self, prec=None, rounding=None, context=None):
@ -1727,10 +1738,9 @@ class Decimal(object):
return Infsign[sign]
return Decimal( (sign, (0,), 0) )
#with ludicrously large exponent, just raise an overflow and return inf.
if not modulo and n > 0 and (self._exp + len(self._int) - 1) * n > context.Emax \
and self:
# With ludicrously large exponent, just raise an overflow and return inf.
if not modulo and n > 0 \
and (self._exp + len(self._int) - 1) * n > context.Emax and self:
tmp = Decimal('inf')
tmp._sign = sign
context._raise_error(Rounded)
@ -1816,7 +1826,7 @@ class Decimal(object):
if exp._isinfinity() or self._isinfinity():
if exp._isinfinity() and self._isinfinity():
return self #if both are inf, it is OK
return self # If both are inf, it is OK
if context is None:
context = getcontext()
return context._raise_error(InvalidOperation,
@ -1848,7 +1858,8 @@ class Decimal(object):
if self._is_special:
if self._isinfinity():
return context._raise_error(InvalidOperation, 'rescale with an INF')
return context._raise_error(InvalidOperation,
'rescale with an INF')
ans = self._check_nans(context=context)
if ans:
@ -1960,7 +1971,6 @@ class Decimal(object):
ans = ans.__add__(tmp.__mul__(Decimal((0, (8,1,9), -3)),
context=context), context=context)
ans._exp -= 1 + tmp.adjusted() // 2
# ans is now a linear approximation.
Emax, Emin = context.Emax, context.Emin
@ -1977,7 +1987,7 @@ class Decimal(object):
if context.prec == maxp:
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
prevexp = ans.adjusted()
ans = ans._round(context=context)
@ -2052,13 +2062,13 @@ class Decimal(object):
ans = self
c = self.__cmp__(other)
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:
#
# 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
#
# 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.
if self._sign != other._sign:
if self._sign:
@ -2079,7 +2089,7 @@ class Decimal(object):
def min(self, other, context=None):
"""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.
"""
other = _convert_other(other)
@ -2087,7 +2097,7 @@ class Decimal(object):
return other
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
sn = self._isnan()
on = other._isnan()
@ -2101,13 +2111,13 @@ class Decimal(object):
ans = self
c = self.__cmp__(other)
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:
#
# 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
#
# 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.
if self._sign != other._sign:
if other._sign:
@ -2146,7 +2156,7 @@ class Decimal(object):
except TypeError:
return 0
# support for pickling, copy, and deepcopy
# Support for pickling, copy, and deepcopy
def __reduce__(self):
return (self.__class__, (str(self),))
@ -2160,19 +2170,20 @@ class Decimal(object):
return self # My components are also immutable
return self.__class__(str(self))
##### Context class ###########################################
##### Context class ##########################################################
# get rounding method function:
rounding_functions = [name for name in Decimal.__dict__.keys() if name.startswith('_round_')]
# Get rounding method function:
rounding_functions = [name for name in Decimal.__dict__.keys()
if name.startswith('_round_')]
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()
val = globals()[globalname]
Decimal._pick_rounding_function[val] = name
del name, val, globalname, rounding_functions
class ContextManager(object):
"""Helper class to simplify Context management.
@ -2197,12 +2208,13 @@ class ContextManager(object):
def __exit__(self, t, v, tb):
setcontext(self.saved_context)
class Context(object):
"""Contains the context for a Decimal instance.
Contains:
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?
traps - If traps[exception] = 1, then the exception is
raised when it is caused. Otherwise, a value is
@ -2243,9 +2255,13 @@ class Context(object):
def __repr__(self):
"""Show the current context."""
s = []
s.append('Context(prec=%(prec)d, rounding=%(rounding)s, Emin=%(Emin)d, Emax=%(Emax)d, capitals=%(capitals)d' % vars(self))
s.append('flags=[' + ', '.join([f.__name__ for f, v in self.flags.items() if v]) + ']')
s.append('traps=[' + ', '.join([t.__name__ for t, v in self.traps.items() if v]) + ']')
s.append(
'Context(prec=%(prec)d, rounding=%(rounding)s, Emin=%(Emin)d, Emax=%(Emax)d, capitals=%(capitals)d'
% vars(self))
s.append('flags=[' + ', '.join([f.__name__ for f, v
in self.flags.items() if v]) + ']')
s.append('traps=[' + ', '.join([t.__name__ for t, v
in self.traps.items() if v]) + ']')
return ', '.join(s) + ')'
def get_manager(self):
@ -2265,9 +2281,10 @@ class Context(object):
def copy(self):
"""Returns a deep copy from self."""
nc = Context(self.prec, self.rounding, self.traps.copy(), self.flags.copy(),
self._rounding_decision, self.Emin, self.Emax,
self.capitals, self._clamp, self._ignored_flags)
nc = Context(self.prec, self.rounding, self.traps.copy(),
self.flags.copy(), self._rounding_decision,
self.Emin, self.Emax, self.capitals,
self._clamp, self._ignored_flags)
return nc
__copy__ = copy
@ -2314,7 +2331,7 @@ class Context(object):
def __hash__(self):
"""A Context cannot be hashed."""
# 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):
"""Returns Etiny (= Emin - prec + 1)"""
@ -2340,7 +2357,6 @@ class Context(object):
This will make it not round for that operation.
"""
rounding = self._rounding_decision
self._rounding_decision = type
return rounding
@ -2528,10 +2544,10 @@ class Context(object):
def multiply(self, a, b):
"""multiply multiplies two operands.
If either operand is a special value then the general rules apply.
Otherwise, the operands are multiplied together ('long multiplication'),
resulting in a number which may be as long as the sum of the lengths
of the two operands.
If either operand is a special value then the general rules
apply. Otherwise, the operands are multiplied together
('long multiplication'), resulting in a number which may be
as long as the sum of the lengths of the two operands.
>>> ExtendedContext.multiply(Decimal('1.20'), Decimal('3'))
Decimal("3.60")
@ -2592,8 +2608,8 @@ class Context(object):
1) before use.
If the increased precision needed for the intermediate calculations
exceeds the capabilities of the implementation then an Invalid operation
condition is raised.
exceeds the capabilities of the implementation then an Invalid
operation condition is raised.
If, when raising to a negative power, an underflow occurs during the
division into 1, the operation is not halted at that point but
@ -2631,7 +2647,7 @@ class Context(object):
return a.__pow__(b, modulo, context=self)
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
operand. It may be rounded using the current rounding setting (if the
@ -2641,8 +2657,8 @@ class Context(object):
Unlike other operations, if the length of the coefficient after the
quantize operation would be greater than precision then an Invalid
operation condition is raised. This guarantees that, unless there is an
error condition, the exponent of the result of a quantize is always
operation condition is raised. This guarantees that, unless there is
an error condition, the exponent of the result of a quantize is always
equal to that of the right-hand operand.
Also unlike other operations, quantize will never raise Underflow, even
@ -2685,9 +2701,9 @@ class Context(object):
"""Returns the remainder from integer division.
The result is the residue of the dividend after the operation of
calculating integer division as described for divide-integer, rounded to
precision digits if necessary. The sign of the result, if non-zero, is
the same as that of the original dividend.
calculating integer division as described for divide-integer, rounded
to precision digits if necessary. The sign of the result, if non-zero,
is the same as that of the original dividend.
This operation will fail under the same conditions as integer division
(that is, if integer division on the same two operands would fail, the
@ -2753,7 +2769,7 @@ class Context(object):
return a.same_quantum(b)
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
algorithm.
@ -2835,6 +2851,7 @@ class Context(object):
"""
return a.to_integral(context=self)
class _WorkRep(object):
__slots__ = ('sign','int','exp')
# sign: 0 or 1
@ -2889,9 +2906,9 @@ def _normalize(op1, op2, shouldround = 0, prec = 0):
other_len = len(str(other.int))
if numdigits > (other_len + prec + 1 - tmp_len):
# If the difference in adjusted exps is > prec+1, we know
# other is insignificant, so might as well put a 1 after the precision.
# (since this is only for addition.) Also stops use of massive longs.
# other is insignificant, so might as well put a 1 after the
# precision (since this is only for addition). Also stops
# use of massive longs.
extend = prec + 2 - tmp_len
if extend <= 0:
extend = 1
@ -2927,7 +2944,8 @@ def _adjust_coefficients(op1, op2):
return op1, op2, adjust
##### Helper Functions ########################################
##### Helper Functions #######################################################
def _convert_other(other):
"""Convert other to Decimal.
@ -2987,7 +3005,7 @@ def _isnan(num):
return 0
##### Setup Specific Contexts ################################
##### Setup Specific Contexts ################################################
# The default context prototype used by Context()
# Is mutable, so that new contexts can have different default values
@ -3020,7 +3038,7 @@ ExtendedContext = Context(
)
##### Useful Constants (internal use only) ####################
##### Useful Constants (internal use only) ###################################
# Reusable defaults
Inf = Decimal('Inf')
@ -3032,7 +3050,7 @@ Infsign = (Inf, negInf)
NaN = Decimal('NaN')
##### crud for parsing strings #################################
##### crud for parsing strings ################################################
import re
# There's an optional sign at the start, and an optional exponent
@ -3052,13 +3070,16 @@ _parser = re.compile(r"""
([eE](?P<exp>[-+]? \d+))?
# \s*
$
""", re.VERBOSE).match #Uncomment the \s* to allow leading or trailing spaces.
""", re.VERBOSE).match # Uncomment the \s* to allow leading/trailing spaces
del re
# return sign, n, p s.t. float string value == -1**sign * n * 10**p exactly
def _string2exact(s):
"""Return sign, n, p s.t.
Float string value == -1**sign * n * 10**p exactly
"""
m = _parser(s)
if m is None:
raise ValueError("invalid literal for Decimal: %r" % s)