Commit 5aa478ba authored by Raymond Hettinger's avatar Raymond Hettinger

Module and tests:

* 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.
parent 563e4497
...@@ -66,10 +66,8 @@ context for arithmetic, and signals. ...@@ -66,10 +66,8 @@ context for arithmetic, and signals.
A decimal number is immutable. It has a sign, coefficient digits, and an A decimal number is immutable. It has a sign, coefficient digits, and an
exponent. To preserve significance, the coefficient digits do not truncate exponent. To preserve significance, the coefficient digits do not truncate
trailing zeroes. Decimals also include special values such as trailing zeroes. Decimals also include special values such as
\constant{Infinity} (the result of \samp{1 / 0}), \constant{-Infinity}, \constant{Infinity}, \constant{-Infinity}, and \constant{NaN}. The standard
(the result of \samp{-1 / 0}), and \constant{NaN} (the result of also differentiates \constant{-0} from \constant{+0}.
\samp{0 / 0}). The standard also differentiates \constant{-0} from
\constant{+0}.
The context for arithmetic is an environment specifying precision, rounding The context for arithmetic is an environment specifying precision, rounding
rules, limits on exponents, flags that indicate the results of operations, rules, limits on exponents, flags that indicate the results of operations,
...@@ -82,9 +80,7 @@ Signals are types of information that arise during the course of a ...@@ -82,9 +80,7 @@ Signals are types of information that arise during the course of a
computation. Depending on the needs of the application, some signals may be computation. Depending on the needs of the application, some signals may be
ignored, considered as informational, or treated as exceptions. The signals in ignored, considered as informational, or treated as exceptions. The signals in
the decimal module are: \constant{Clamped}, \constant{InvalidOperation}, the decimal module are: \constant{Clamped}, \constant{InvalidOperation},
\constant{ConversionSyntax}, \constant{DivisionByZero}, \constant{DivisionByZero}, \constant{Inexact}, \constant{Rounded},
\constant{DivisionImpossible}, \constant{DivisionUndefined},
\constant{Inexact}, \constant{InvalidContext}, \constant{Rounded},
\constant{Subnormal}, \constant{Overflow}, and \constant{Underflow}. \constant{Subnormal}, \constant{Overflow}, and \constant{Underflow}.
For each signal there is a flag and a trap enabler. When a signal is For each signal there is a flag and a trap enabler. When a signal is
...@@ -124,7 +120,7 @@ Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, ...@@ -124,7 +120,7 @@ Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999,
Decimal instances can be constructed from integers, strings or tuples. To 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 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 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 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 ``Not a number''. Positive and negative \constant{Infinity} is yet another
special kind of Decimal. special kind of Decimal.
...@@ -274,10 +270,8 @@ To turn all the traps on or off all at once, use a loop. Also, the ...@@ -274,10 +270,8 @@ To turn all the traps on or off all at once, use a loop. Also, the
>>> getcontext().trap_enablers.update({Rounded:0, Inexact:0, Subnormal:0}) >>> getcontext().trap_enablers.update({Rounded:0, Inexact:0, Subnormal:0})
>>> getcontext() >>> getcontext()
Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999,
setflags=[], settraps=['Underflow', 'DecimalException', 'Clamped', setflags=[], settraps=['Clamped', 'Underflow', 'InvalidOperation',
'InvalidContext', 'InvalidOperation', 'ConversionSyntax', 'DivisionByZero', 'Overflow'])
'DivisionByZero', 'DivisionImpossible', 'DivisionUndefined',
'Overflow'])
\end{verbatim} \end{verbatim}
Applications typically set the context once at the beginning of a program Applications typically set the context once at the beginning of a program
...@@ -320,7 +314,7 @@ as other Python numeric types. ...@@ -320,7 +314,7 @@ as other Python numeric types.
The supplied \var{context} or, if not specified, the current context The supplied \var{context} or, if not specified, the current context
governs only the handling of malformed strings not conforming to the 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 an exception is raised; otherwise, the constructor returns a new Decimal
with the value of \constant{NaN}. with the value of \constant{NaN}.
...@@ -730,13 +724,6 @@ exception is raised upon encountering the condition. ...@@ -730,13 +724,6 @@ exception is raised upon encountering the condition.
reduced to fit by adding zeroes to the coefficient. reduced to fit by adding zeroes to the coefficient.
\end{classdesc*} \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} \begin{classdesc*}{DecimalException}
Base class for other signals. Base class for other signals.
\end{classdesc*} \end{classdesc*}
...@@ -750,19 +737,6 @@ exception is raised upon encountering the condition. ...@@ -750,19 +737,6 @@ exception is raised upon encountering the condition.
the inputs to the calculation. the inputs to the calculation.
\end{classdesc*} \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} \begin{classdesc*}{Inexact}
Indicates that rounding occurred and the result is not exact. Indicates that rounding occurred and the result is not exact.
...@@ -771,14 +745,6 @@ exception is raised upon encountering the condition. ...@@ -771,14 +745,6 @@ exception is raised upon encountering the condition.
to detect when results are inexact. to detect when results are inexact.
\end{classdesc*} \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} \begin{classdesc*}{InvalidOperation}
An invalid operation was performed. An invalid operation was performed.
...@@ -809,7 +775,6 @@ exception is raised upon encountering the condition. ...@@ -809,7 +775,6 @@ exception is raised upon encountering the condition.
\class{Rounded} are also signaled. \class{Rounded} are also signaled.
\end{classdesc*} \end{classdesc*}
\begin{classdesc*}{Rounded} \begin{classdesc*}{Rounded}
Rounding occurred though possibly no information was lost. Rounding occurred though possibly no information was lost.
...@@ -844,16 +809,11 @@ The following table summarizes the hierarchy of signals: ...@@ -844,16 +809,11 @@ The following table summarizes the hierarchy of signals:
Overflow(Inexact, Rounded) Overflow(Inexact, Rounded)
Underflow(Inexact, Rounded, Subnormal) Underflow(Inexact, Rounded, Subnormal)
InvalidOperation InvalidOperation
ConversionSyntax
DivisionImpossible
DivisionUndefined(InvalidOperation, exceptions.ZeroDivisionError)
InvalidContext
Rounded Rounded
Subnormal Subnormal
\end{verbatim} \end{verbatim}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Working with threads \label{decimal-threads}} \subsection{Working with threads \label{decimal-threads}}
......
...@@ -78,30 +78,30 @@ Traceback (most recent call last): ...@@ -78,30 +78,30 @@ Traceback (most recent call last):
... ...
DivisionByZero: x / 0 DivisionByZero: x / 0
>>> c = Context() >>> c = Context()
>>> c.trap_enablers[DivisionUndefined] = 0 >>> c.trap_enablers[InvalidOperation] = 0
>>> print c.flags[DivisionUndefined] >>> print c.flags[InvalidOperation]
0 0
>>> c.divide(Decimal(0), Decimal(0)) >>> c.divide(Decimal(0), Decimal(0))
Decimal("NaN") Decimal("NaN")
>>> c.trap_enablers[DivisionUndefined] = 1 >>> c.trap_enablers[InvalidOperation] = 1
>>> print c.flags[DivisionUndefined] >>> print c.flags[InvalidOperation]
1 1
>>> c.flags[DivisionUndefined] = 0 >>> c.flags[InvalidOperation] = 0
>>> print c.flags[DivisionUndefined] >>> print c.flags[InvalidOperation]
0 0
>>> print c.divide(Decimal(0), Decimal(0)) >>> print c.divide(Decimal(0), Decimal(0))
Traceback (most recent call last): Traceback (most recent call last):
... ...
... ...
... ...
DivisionUndefined: 0 / 0 InvalidOperation: 0 / 0
>>> print c.flags[DivisionUndefined] >>> print c.flags[InvalidOperation]
1 1
>>> c.flags[DivisionUndefined] = 0 >>> c.flags[InvalidOperation] = 0
>>> c.trap_enablers[DivisionUndefined] = False >>> c.trap_enablers[InvalidOperation] = 0
>>> print c.divide(Decimal(0), Decimal(0)) >>> print c.divide(Decimal(0), Decimal(0))
NaN NaN
>>> print c.flags[DivisionUndefined] >>> print c.flags[InvalidOperation]
1 1
>>> >>>
""" """
...@@ -152,7 +152,7 @@ ALWAYS_ROUND = 'ALWAYS_ROUND' # Every operation rounds at end. ...@@ -152,7 +152,7 @@ ALWAYS_ROUND = 'ALWAYS_ROUND' # Every operation rounds at end.
#Errors #Errors
class DecimalException(ArithmeticError): class DecimalException(ArithmeticError):
"""Base exception class, defines default things. """Base exception class.
Used exceptions derive from this. Used exceptions derive from this.
If an exception derives from another exception besides this (such as If an exception derives from another exception besides this (such as
...@@ -160,12 +160,6 @@ class DecimalException(ArithmeticError): ...@@ -160,12 +160,6 @@ class DecimalException(ArithmeticError):
called if the others are present. This isn't actually used for called if the others are present. This isn't actually used for
anything, though. 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 handle -- Called when context._raise_error is called and the
trap_enabler is set. First argument is self, second is the trap_enabler is set. First argument is self, second is the
context. More arguments can be given, those being after context. More arguments can be given, those being after
...@@ -176,7 +170,6 @@ class DecimalException(ArithmeticError): ...@@ -176,7 +170,6 @@ class DecimalException(ArithmeticError):
To define a new exception, it should be sufficient to have it derive To define a new exception, it should be sufficient to have it derive
from DecimalException. from DecimalException.
""" """
default = 1
def handle(self, context, *args): def handle(self, context, *args):
pass pass
...@@ -288,7 +281,7 @@ class Inexact(DecimalException): ...@@ -288,7 +281,7 @@ class Inexact(DecimalException):
The inexact signal may be tested (or trapped) to determine if a given The inexact signal may be tested (or trapped) to determine if a given
operation (or sequence of operations) was inexact. operation (or sequence of operations) was inexact.
""" """
default = 0 pass
class InvalidContext(InvalidOperation): class InvalidContext(InvalidOperation):
"""Invalid context. Unknown rounding, for example. """Invalid context. Unknown rounding, for example.
...@@ -315,7 +308,7 @@ class Rounded(DecimalException): ...@@ -315,7 +308,7 @@ class Rounded(DecimalException):
The rounded signal may be tested (or trapped) to determine if a given The rounded signal may be tested (or trapped) to determine if a given
operation (or sequence of operations) caused a loss of precision. operation (or sequence of operations) caused a loss of precision.
""" """
default = 0 pass
class Subnormal(DecimalException): class Subnormal(DecimalException):
"""Exponent < Emin before rounding. """Exponent < Emin before rounding.
...@@ -382,19 +375,15 @@ class Underflow(Inexact, Rounded, Subnormal): ...@@ -382,19 +375,15 @@ class Underflow(Inexact, Rounded, Subnormal):
In all cases, Inexact, Rounded, and Subnormal will also be raised. 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): # Map conditions (per the spec) to signals
"""Returns true if a subclass of DecimalException""" _condition_map = {ConversionSyntax:InvalidOperation,
try: DivisionImpossible:InvalidOperation,
return issubclass(obj, DecimalException) DivisionUndefined:InvalidOperation,
except TypeError: InvalidContext:InvalidOperation}
return False
#Signals holds the exceptions
Signals = filter(_filterfunc, globals().values())
del _filterfunc
##### Context Functions ####################################### ##### Context Functions #######################################
...@@ -2168,7 +2157,7 @@ class Context(object): ...@@ -2168,7 +2157,7 @@ class Context(object):
return nc return nc
__copy__ = copy __copy__ = copy
def _raise_error(self, error, explanation = None, *args): def _raise_error(self, condition, explanation = None, *args):
"""Handles an error """Handles an error
If the flag is in _ignored_flags, returns the default response. If the flag is in _ignored_flags, returns the default response.
...@@ -2176,6 +2165,7 @@ class Context(object): ...@@ -2176,6 +2165,7 @@ class Context(object):
trap_enabler is set, it reaises the exception. Otherwise, it returns trap_enabler is set, it reaises the exception. Otherwise, it returns
the default value after incrementing the flag. the default value after incrementing the flag.
""" """
error = _condition_map.get(condition, condition)
if error in self._ignored_flags: if error in self._ignored_flags:
#Don't touch the flag #Don't touch the flag
return error().handle(self, *args) return error().handle(self, *args)
...@@ -2183,7 +2173,7 @@ class Context(object): ...@@ -2183,7 +2173,7 @@ class Context(object):
self.flags[error] += 1 self.flags[error] += 1
if not self.trap_enablers[error]: if not self.trap_enablers[error]:
#The errors define how to handle themselves. #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 # Errors should only be risked on copies of the context
#self._ignored_flags = [] #self._ignored_flags = []
...@@ -2207,6 +2197,11 @@ class Context(object): ...@@ -2207,6 +2197,11 @@ class Context(object):
for flag in flags: for flag in flags:
self._ignored_flags.remove(flag) 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): def Etiny(self):
"""Returns Etiny (= Emin - prec + 1)""" """Returns Etiny (= Emin - prec + 1)"""
return int(self.Emin - self.prec + 1) return int(self.Emin - self.prec + 1)
......
...@@ -60,12 +60,12 @@ EXTENDEDERRORTEST = False ...@@ -60,12 +60,12 @@ EXTENDEDERRORTEST = False
#Map the test cases' error names to the actual errors #Map the test cases' error names to the actual errors
ErrorNames = {'clamped' : Clamped, ErrorNames = {'clamped' : Clamped,
'conversion_syntax' : ConversionSyntax, 'conversion_syntax' : InvalidOperation,
'division_by_zero' : DivisionByZero, 'division_by_zero' : DivisionByZero,
'division_impossible' : DivisionImpossible, 'division_impossible' : InvalidOperation,
'division_undefined' : DivisionUndefined, 'division_undefined' : InvalidOperation,
'inexact' : Inexact, 'inexact' : Inexact,
'invalid_context' : InvalidContext, 'invalid_context' : InvalidOperation,
'invalid_operation' : InvalidOperation, 'invalid_operation' : InvalidOperation,
'overflow' : Overflow, 'overflow' : Overflow,
'rounded' : Rounded, 'rounded' : Rounded,
...@@ -131,6 +131,7 @@ class DecimalTest(unittest.TestCase): ...@@ -131,6 +131,7 @@ class DecimalTest(unittest.TestCase):
return return
for line in open(file).xreadlines(): for line in open(file).xreadlines():
line = line.replace('\r\n', '').replace('\n', '') line = line.replace('\r\n', '').replace('\n', '')
#print line
try: try:
t = self.eval_line(line) t = self.eval_line(line)
except ConversionSyntax: except ConversionSyntax:
...@@ -648,7 +649,6 @@ class DecimalArithmeticOperatorsTest(unittest.TestCase): ...@@ -648,7 +649,6 @@ class DecimalArithmeticOperatorsTest(unittest.TestCase):
self.assertEqual(d1, Decimal('-0.625')) self.assertEqual(d1, Decimal('-0.625'))
def test_floor_division(self): def test_floor_division(self):
'''Test floor division in all its ways.'''
d1 = Decimal('5') d1 = Decimal('5')
d2 = Decimal('2') d2 = Decimal('2')
...@@ -676,7 +676,6 @@ class DecimalArithmeticOperatorsTest(unittest.TestCase): ...@@ -676,7 +676,6 @@ class DecimalArithmeticOperatorsTest(unittest.TestCase):
self.assertEqual(d1, Decimal('1')) self.assertEqual(d1, Decimal('1'))
def test_powering(self): def test_powering(self):
'''Test powering in all its ways.'''
d1 = Decimal('5') d1 = Decimal('5')
d2 = Decimal('2') d2 = Decimal('2')
......
...@@ -26,6 +26,9 @@ Extension modules ...@@ -26,6 +26,9 @@ Extension modules
Library Library
------- -------
- decimal.py now only uses signals in the spec. The other conditions are
no longer part of the public API.
Tools/Demos Tools/Demos
----------- -----------
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment