* Map conditions to related signals.
* Make contexts unhashable.
* Eliminate used "default" attribute in exception definitions.
* Eliminate the _filterfunc in favor of a straight list.
Docs:
* Eliminate documented references to conditions that are not signals.
* Eliminate parenthetical notes such as "1/0 --> Inf" which are no
longer true with the new defaults.
A decimal number is immutable. It has a sign, coefficient digits, and an
exponent. To preserve significance, the coefficient digits do not truncate
trailing zeroes. Decimals also include special values such as
-\constant{Infinity} (the result of \samp{1 / 0}), \constant{-Infinity},
-(the result of \samp{-1 / 0}), and \constant{NaN} (the result of
-\samp{0 / 0}). The standard also differentiates \constant{-0} from
-\constant{+0}.
+\constant{Infinity}, \constant{-Infinity}, and \constant{NaN}. The standard
+also differentiates \constant{-0} from \constant{+0}.
The context for arithmetic is an environment specifying precision, rounding
rules, limits on exponents, flags that indicate the results of operations,
computation. Depending on the needs of the application, some signals may be
ignored, considered as informational, or treated as exceptions. The signals in
the decimal module are: \constant{Clamped}, \constant{InvalidOperation},
-\constant{ConversionSyntax}, \constant{DivisionByZero},
-\constant{DivisionImpossible}, \constant{DivisionUndefined},
-\constant{Inexact}, \constant{InvalidContext}, \constant{Rounded},
+\constant{DivisionByZero}, \constant{Inexact}, \constant{Rounded},
\constant{Subnormal}, \constant{Overflow}, and \constant{Underflow}.
For each signal there is a flag and a trap enabler. When a signal is
Decimal instances can be constructed from integers, strings or tuples. To
create a Decimal from a \class{float}, first convert it to a string. This
serves as an explicit reminder of the details of the conversion (including
-representation error). Malformed strings signal \constant{ConversionSyntax}
+representation error). Malformed strings signal \constant{InvalidOperation}
and return a special kind of Decimal called a \constant{NaN} which stands for
``Not a number''. Positive and negative \constant{Infinity} is yet another
special kind of Decimal.
>>> getcontext().trap_enablers.update({Rounded:0, Inexact:0, Subnormal:0})
>>> getcontext()
Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999,
- setflags=[], settraps=['Underflow', 'DecimalException', 'Clamped',
- 'InvalidContext', 'InvalidOperation', 'ConversionSyntax',
- 'DivisionByZero', 'DivisionImpossible', 'DivisionUndefined',
- 'Overflow'])
+ setflags=[], settraps=['Clamped', 'Underflow', 'InvalidOperation',
+ 'DivisionByZero', 'Overflow'])
\end{verbatim}
Applications typically set the context once at the beginning of a program
The supplied \var{context} or, if not specified, the current context
governs only the handling of malformed strings not conforming to the
- numeric string syntax. If the context traps \constant{ConversionSyntax},
+ numeric string syntax. If the context traps \constant{InvalidOperation},
an exception is raised; otherwise, the constructor returns a new Decimal
with the value of \constant{NaN}.
reduced to fit by adding zeroes to the coefficient.
\end{classdesc*}
-\begin{classdesc*}{ConversionSyntax}
- Trying to convert a malformed string such as: \code{Decimal('jump')}.
-
- Decimal converts only strings conforming to the numeric string
- syntax. If this signal is not trapped, returns \constant{NaN}.
-\end{classdesc*}
-
\begin{classdesc*}{DecimalException}
Base class for other signals.
\end{classdesc*}
the inputs to the calculation.
\end{classdesc*}
-\begin{classdesc*}{DivisionImpossible}
- Error performing a division operation. Caused when an intermediate result
- has more digits that the allowed by the current precision. If not trapped,
- returns \constant{NaN}.
-\end{classdesc*}
-
-
-\begin{classdesc*}{DivisionUndefined}
- This is a subclass of \class{DivisionByZero}.
-
- It occurs only in the context of division operations.
-\end{classdesc*}
-
\begin{classdesc*}{Inexact}
Indicates that rounding occurred and the result is not exact.
to detect when results are inexact.
\end{classdesc*}
-
-\begin{classdesc*}{InvalidContext}
- This is a subclass of \class{InvalidOperation}.
-
- Indicates an error within the Context object such as an unknown
- rounding operation. If not trapped, returns \constant{NaN}.
-\end{classdesc*}
-
\begin{classdesc*}{InvalidOperation}
An invalid operation was performed.
\class{Rounded} are also signaled.
\end{classdesc*}
-
\begin{classdesc*}{Rounded}
Rounding occurred though possibly no information was lost.
Overflow(Inexact, Rounded)
Underflow(Inexact, Rounded, Subnormal)
InvalidOperation
- ConversionSyntax
- DivisionImpossible
- DivisionUndefined(InvalidOperation, exceptions.ZeroDivisionError)
- InvalidContext
Rounded
Subnormal
\end{verbatim}
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Working with threads \label{decimal-threads}}
...
DivisionByZero: x / 0
>>> c = Context()
->>> c.trap_enablers[DivisionUndefined] = 0
->>> print c.flags[DivisionUndefined]
+>>> c.trap_enablers[InvalidOperation] = 0
+>>> print c.flags[InvalidOperation]
0
>>> c.divide(Decimal(0), Decimal(0))
Decimal("NaN")
->>> c.trap_enablers[DivisionUndefined] = 1
->>> print c.flags[DivisionUndefined]
+>>> c.trap_enablers[InvalidOperation] = 1
+>>> print c.flags[InvalidOperation]
1
->>> c.flags[DivisionUndefined] = 0
->>> print c.flags[DivisionUndefined]
+>>> c.flags[InvalidOperation] = 0
+>>> print c.flags[InvalidOperation]
0
>>> print c.divide(Decimal(0), Decimal(0))
Traceback (most recent call last):
...
...
...
-DivisionUndefined: 0 / 0
->>> print c.flags[DivisionUndefined]
+InvalidOperation: 0 / 0
+>>> print c.flags[InvalidOperation]
1
->>> c.flags[DivisionUndefined] = 0
->>> c.trap_enablers[DivisionUndefined] = False
+>>> c.flags[InvalidOperation] = 0
+>>> c.trap_enablers[InvalidOperation] = 0
>>> print c.divide(Decimal(0), Decimal(0))
NaN
->>> print c.flags[DivisionUndefined]
+>>> print c.flags[InvalidOperation]
1
>>>
"""
#Errors
class DecimalException(ArithmeticError):
- """Base exception class, defines default things.
+ """Base exception class.
Used exceptions derive from this.
If an exception derives from another exception besides this (such as
called if the others are present. This isn't actually used for
anything, though.
- Attributes:
-
- default -- If the context is basic, the trap_enablers are set to
- this by default. Extended contexts start out with them set
- to 0, regardless.
-
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
To define a new exception, it should be sufficient to have it derive
from DecimalException.
"""
- default = 1
def handle(self, context, *args):
pass
The inexact signal may be tested (or trapped) to determine if a given
operation (or sequence of operations) was inexact.
"""
- default = 0
+ pass
class InvalidContext(InvalidOperation):
"""Invalid context. Unknown rounding, for example.
The rounded signal may be tested (or trapped) to determine if a given
operation (or sequence of operations) caused a loss of precision.
"""
- default = 0
+ pass
class Subnormal(DecimalException):
"""Exponent < Emin before rounding.
In all cases, Inexact, Rounded, and Subnormal will also be raised.
"""
+# List of public traps and flags
+Signals = [Clamped, DivisionByZero, Inexact, Overflow, Rounded,
+ Underflow, InvalidOperation, Subnormal]
-def _filterfunc(obj):
- """Returns true if a subclass of DecimalException"""
- try:
- return issubclass(obj, DecimalException)
- except TypeError:
- return False
-
-#Signals holds the exceptions
-Signals = filter(_filterfunc, globals().values())
-
-del _filterfunc
-
+# Map conditions (per the spec) to signals
+_condition_map = {ConversionSyntax:InvalidOperation,
+ DivisionImpossible:InvalidOperation,
+ DivisionUndefined:InvalidOperation,
+ InvalidContext:InvalidOperation}
##### Context Functions #######################################
return nc
__copy__ = copy
- def _raise_error(self, error, explanation = None, *args):
+ def _raise_error(self, condition, explanation = None, *args):
"""Handles an error
If the flag is in _ignored_flags, returns the default response.
trap_enabler is set, it reaises the exception. Otherwise, it returns
the default value after incrementing the flag.
"""
+ error = _condition_map.get(condition, condition)
if error in self._ignored_flags:
#Don't touch the flag
return error().handle(self, *args)
self.flags[error] += 1
if not self.trap_enablers[error]:
#The errors define how to handle themselves.
- return error().handle(self, *args)
+ return condition().handle(self, *args)
# Errors should only be risked on copies of the context
#self._ignored_flags = []
for flag in flags:
self._ignored_flags.remove(flag)
+ def __hash__(self):
+ """A Context cannot be hashed."""
+ # We inherit object.__hash__, so we must deny this explicitly
+ raise TypeError, "Cannot hash a Context."
+
def Etiny(self):
"""Returns Etiny (= Emin - prec + 1)"""
return int(self.Emin - self.prec + 1)
#Map the test cases' error names to the actual errors
ErrorNames = {'clamped' : Clamped,
- 'conversion_syntax' : ConversionSyntax,
+ 'conversion_syntax' : InvalidOperation,
'division_by_zero' : DivisionByZero,
- 'division_impossible' : DivisionImpossible,
- 'division_undefined' : DivisionUndefined,
+ 'division_impossible' : InvalidOperation,
+ 'division_undefined' : InvalidOperation,
'inexact' : Inexact,
- 'invalid_context' : InvalidContext,
+ 'invalid_context' : InvalidOperation,
'invalid_operation' : InvalidOperation,
'overflow' : Overflow,
'rounded' : Rounded,
return
for line in open(file).xreadlines():
line = line.replace('\r\n', '').replace('\n', '')
+ #print line
try:
t = self.eval_line(line)
except ConversionSyntax:
self.assertEqual(d1, Decimal('-0.625'))
def test_floor_division(self):
- '''Test floor division in all its ways.'''
d1 = Decimal('5')
d2 = Decimal('2')
self.assertEqual(d1, Decimal('1'))
def test_powering(self):
- '''Test powering in all its ways.'''
d1 = Decimal('5')
d2 = Decimal('2')
Library
-------
+- decimal.py now only uses signals in the spec. The other conditions are
+ no longer part of the public API.
+
Tools/Demos
-----------