From 5aa478badfabb005e6f17c98fcf007a0bc54eecc Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Fri, 9 Jul 2004 10:02:53 +0000 Subject: 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. --- Doc/lib/libdecimal.tex | 54 ++++++---------------------------------- Lib/decimal.py | 65 ++++++++++++++++++++++-------------------------- Lib/test/test_decimal.py | 11 ++++---- Misc/NEWS | 3 +++ 4 files changed, 45 insertions(+), 88 deletions(-) diff --git a/Doc/lib/libdecimal.tex b/Doc/lib/libdecimal.tex index 3b41d6a..b6164fc 100644 --- a/Doc/lib/libdecimal.tex +++ b/Doc/lib/libdecimal.tex @@ -66,10 +66,8 @@ context for arithmetic, and signals. 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, @@ -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 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 @@ -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 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. @@ -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() 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 @@ -320,7 +314,7 @@ as other Python numeric types. 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}. @@ -730,13 +724,6 @@ exception is raised upon encountering the condition. 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*} @@ -750,19 +737,6 @@ exception is raised upon encountering the condition. 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. @@ -771,14 +745,6 @@ exception is raised upon encountering the condition. 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. @@ -809,7 +775,6 @@ exception is raised upon encountering the condition. \class{Rounded} are also signaled. \end{classdesc*} - \begin{classdesc*}{Rounded} Rounding occurred though possibly no information was lost. @@ -844,16 +809,11 @@ The following table summarizes the hierarchy of signals: 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}} diff --git a/Lib/decimal.py b/Lib/decimal.py index 4e5cbbb..9f77b24 100644 --- a/Lib/decimal.py +++ b/Lib/decimal.py @@ -78,30 +78,30 @@ Traceback (most recent call last): ... 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 >>> """ @@ -152,7 +152,7 @@ ALWAYS_ROUND = 'ALWAYS_ROUND' # Every operation rounds at end. #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 @@ -160,12 +160,6 @@ class DecimalException(ArithmeticError): 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 @@ -176,7 +170,6 @@ class DecimalException(ArithmeticError): To define a new exception, it should be sufficient to have it derive from DecimalException. """ - default = 1 def handle(self, context, *args): pass @@ -288,7 +281,7 @@ class Inexact(DecimalException): 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. @@ -315,7 +308,7 @@ class Rounded(DecimalException): 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. @@ -382,19 +375,15 @@ class Underflow(Inexact, Rounded, Subnormal): 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 ####################################### @@ -2168,7 +2157,7 @@ class Context(object): 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. @@ -2176,6 +2165,7 @@ class Context(object): 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) @@ -2183,7 +2173,7 @@ class Context(object): 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 = [] @@ -2207,6 +2197,11 @@ class Context(object): 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) diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index a03b784..e6d9389 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -60,12 +60,12 @@ EXTENDEDERRORTEST = False #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, @@ -131,6 +131,7 @@ class DecimalTest(unittest.TestCase): return for line in open(file).xreadlines(): line = line.replace('\r\n', '').replace('\n', '') + #print line try: t = self.eval_line(line) except ConversionSyntax: @@ -648,7 +649,6 @@ class DecimalArithmeticOperatorsTest(unittest.TestCase): self.assertEqual(d1, Decimal('-0.625')) def test_floor_division(self): - '''Test floor division in all its ways.''' d1 = Decimal('5') d2 = Decimal('2') @@ -676,7 +676,6 @@ class DecimalArithmeticOperatorsTest(unittest.TestCase): self.assertEqual(d1, Decimal('1')) def test_powering(self): - '''Test powering in all its ways.''' d1 = Decimal('5') d2 = Decimal('2') diff --git a/Misc/NEWS b/Misc/NEWS index df3dcca..b7a1ade 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -26,6 +26,9 @@ Extension modules Library ------- +- decimal.py now only uses signals in the spec. The other conditions are + no longer part of the public API. + Tools/Demos ----------- -- cgit v0.12