3137 lines · 104.8 KB · Python
RawDownload
# Copyright (c) 2004 Python Software Foundation.# All rights reserved. # Written by Eric Price <eprice at tjhsst.edu>#    and Facundo Batista <facundo at taniquetil.com.ar>#    and Raymond Hettinger <python at rcn.com>#    and Aahz <aahz at pobox.com>#    and Tim Peters # This module is currently Py2.3 compatible and should be kept that way# unless a major compelling advantage arises.  IOW, 2.3 compatibility is# strongly preferred, but not guaranteed. # Also, this module should be kept in sync with the latest updates of# the IBM specification as it evolves.  Those updates will be treated# as bug fixes (deviation from the spec is a compatibility, usability# bug) and will be backported.  At this point the spec is stabilizing# and the updates are becoming fewer, smaller, and less significant. """This is a Py2.3 implementation of decimal floating point arithmetic based onthe General Decimal Arithmetic Specification:     www2.hursley.ibm.com/decimal/decarith.html and IEEE standard 854-1987:     www.cs.berkeley.edu/~ejr/projects/754/private/drafts/854-1987/dir.html 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 representationissues associated with binary floating point.  The package is especiallyuseful for financial applications or for contexts where users haveexpectations that are at odds with binary floating point (for instance,in binary floating point, 1.00 % 0.1 gives 0.09999999999999995 insteadof the expected Decimal("0.00") returned by decimal floating point). Here are some examples of using the decimal module: >>> from decimal import *>>> setcontext(ExtendedContext)>>> Decimal(0)Decimal("0")>>> Decimal("1")Decimal("1")>>> Decimal("-.0123")Decimal("-0.0123")>>> Decimal(123456)Decimal("123456")>>> Decimal("123.45e12345678901234567890")Decimal("1.2345E+12345678901234567892")>>> Decimal("1.33") + Decimal("1.27")Decimal("2.60")>>> Decimal("12.34") + Decimal("3.87") - Decimal("18.41")Decimal("-2.20")>>> dig = Decimal(1)>>> print dig / Decimal(3)0.333333333>>> getcontext().prec = 18>>> print dig / Decimal(3)0.333333333333333333>>> print dig.sqrt()1>>> print Decimal(3).sqrt()1.73205080756887729>>> print Decimal(3) ** 1234.85192780976896427E+58>>> inf = Decimal(1) / Decimal(0)>>> print infInfinity>>> neginf = Decimal(-1) / Decimal(0)>>> print neginf-Infinity>>> print neginf + infNaN>>> print neginf * inf-Infinity>>> print dig / 0Infinity>>> getcontext().traps[DivisionByZero] = 1>>> print dig / 0Traceback (most recent call last):  ...  ...  ...DivisionByZero: x / 0>>> c = Context()>>> c.traps[InvalidOperation] = 0>>> print c.flags[InvalidOperation]0>>> c.divide(Decimal(0), Decimal(0))Decimal("NaN")>>> c.traps[InvalidOperation] = 1>>> print c.flags[InvalidOperation]1>>> c.flags[InvalidOperation] = 0>>> print c.flags[InvalidOperation]0>>> print c.divide(Decimal(0), Decimal(0))Traceback (most recent call last):  ...  ...  ...InvalidOperation: 0 / 0>>> print c.flags[InvalidOperation]1>>> c.flags[InvalidOperation] = 0>>> c.traps[InvalidOperation] = 0>>> print c.divide(Decimal(0), Decimal(0))NaN>>> print c.flags[InvalidOperation]1>>>""" __all__ = [    # Two major classes    'Decimal', 'Context',     # Contexts    'DefaultContext', 'BasicContext', 'ExtendedContext',     # Exceptions    'DecimalException', 'Clamped', 'InvalidOperation', 'DivisionByZero',    'Inexact', 'Rounded', 'Subnormal', 'Overflow', 'Underflow',     # Constants for use in setting up contexts    'ROUND_DOWN', 'ROUND_HALF_UP', 'ROUND_HALF_EVEN', 'ROUND_CEILING',    'ROUND_FLOOR', 'ROUND_UP', 'ROUND_HALF_DOWN',     # Functions for manipulating contexts    'setcontext', 'getcontext', 'localcontext'] import copy as _copy #RoundingROUND_DOWN = 'ROUND_DOWN'ROUND_HALF_UP = 'ROUND_HALF_UP'ROUND_HALF_EVEN = 'ROUND_HALF_EVEN'ROUND_CEILING = 'ROUND_CEILING'ROUND_FLOOR = 'ROUND_FLOOR'ROUND_UP = 'ROUND_UP'ROUND_HALF_DOWN = 'ROUND_HALF_DOWN' #Rounding decision (not part of the public API)NEVER_ROUND = 'NEVER_ROUND'    # Round in division (non-divmod), sqrt ONLYALWAYS_ROUND = 'ALWAYS_ROUND'  # Every operation rounds at end. #Errors class DecimalException(ArithmeticError):    """Base exception class.     Used exceptions derive from this.    If an exception derives from another exception besides this (such as    Underflow (Inexact, Rounded, Subnormal) that indicates that it is only    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):        pass  class Clamped(DecimalException):    """Exponent of a 0 changed to fit bounds.     This occurs and signals clamped if the exponent of a result has been    altered in order to fit the constraints of a specific concrete    representation. This may occur when the exponent of a zero result would    be outside the bounds of a representation, or  when a large normal    number would have an encoded exponent that cannot be represented. In    this latter case, the exponent is reduced to fit and the corresponding    number of zero digits are appended to the coefficient ("fold-down").    """  class InvalidOperation(DecimalException):    """An invalid operation was performed.     Various bad things cause this:     Something creates a signaling NaN    -INF + INF     0 * (+-)INF     (+-)INF / (+-)INF    x % 0    (+-)INF % x    x._rescale( non-integer )    sqrt(-x) , x > 0    0 ** 0    x ** (non-integer)    x ** (+-)INF    An operand is invalid    """    def handle(self, context, *args):        if args:            if args[0] == 1: #sNaN, must drop 's' but keep diagnostics                return Decimal( (args[1]._sign, args[1]._int, 'n') )        return NaN class ConversionSyntax(InvalidOperation):    """Trying to convert badly formed string.     This occurs and signals invalid-operation if an string is being    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.     This occurs and signals division-by-zero if division of a finite number    by zero was attempted (during a divide-integer or divide operation, or a    power operation with negative right-hand operand), and the dividend was    not zero.     The result of the operation is [sign,inf], where sign is the exclusive    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.     This occurs and signals invalid-operation if the integer result of a    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.     This occurs and signals invalid-operation if division by zero was    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 class Inexact(DecimalException):    """Had to round, losing information.     This occurs and signals inexact whenever the result of an operation is    not exact (that is, it needed to be rounded and any discarded digits    were non-zero), or if an overflow or underflow condition occurs. The    result in all cases is unchanged.     The inexact signal may be tested (or trapped) to determine if a given    operation (or sequence of operations) was inexact.    """    pass class InvalidContext(InvalidOperation):    """Invalid context.  Unknown rounding, for example.     This occurs and signals invalid-operation if an invalid context was    detected during an operation. This can occur if contexts are not checked    on creation and either the precision exceeds the capability of the    underlying concrete representation or an unknown or unsupported rounding    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).     This occurs and signals rounded whenever the result of an operation is    rounded (that is, some zero or non-zero digits were discarded from the    coefficient), or if an overflow or underflow condition occurs. The    result in all cases is unchanged.     The rounded signal may be tested (or trapped) to determine if a given    operation (or sequence of operations) caused a loss of precision.    """    pass class Subnormal(DecimalException):    """Exponent < Emin before rounding.     This occurs and signals subnormal whenever the result of a conversion or    operation is subnormal (that is, its adjusted exponent is less than    Emin, before any rounding). The result in all cases is unchanged.     The subnormal signal may be tested (or trapped) to determine if a given    or operation (or sequence of operations) yielded a subnormal result.    """    pass class Overflow(Inexact, Rounded):    """Numerical overflow.     This occurs and signals overflow if the adjusted exponent of a result    (from a conversion or from an operation that is not an attempt to divide    by zero), after rounding, would be greater than the largest value that    can be handled by the implementation (the value Emax).     The result depends on the rounding mode:     For round-half-up and round-half-even (and for round-half-down and    round-up, if implemented), the result of the operation is [sign,inf],    where sign is the sign of the intermediate result. For round-down, the    result is the largest finite number that can be represented in the    current precision, with the sign of the intermediate result. For    round-ceiling, the result is the same as for round-down if the sign of    the intermediate result is 1, or is [0,inf] otherwise. For round-floor,    the result is the same as for round-down if the sign of the intermediate    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):            return Infsign[sign]        if sign == 0:            if context.rounding == ROUND_CEILING:                return Infsign[sign]            return Decimal((sign, (9,)*context.prec,                            context.Emax-context.prec+1))        if sign == 1:            if context.rounding == ROUND_FLOOR:                return Infsign[sign]            return Decimal( (sign, (9,)*context.prec,                             context.Emax-context.prec+1))  class Underflow(Inexact, Rounded, Subnormal):    """Numerical underflow with result rounded to 0.     This occurs and signals underflow if a result is inexact and the    adjusted exponent of the result would be smaller (more negative) than    the smallest value that can be handled by the implementation (the value    Emin). That is, the result is both inexact and subnormal.     The result after an underflow will be a subnormal number rounded, if    necessary, so that its exponent is not less than Etiny. This may result    in 0 with the sign of the intermediate result and an exponent of Etiny.     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] # Map conditions (per the spec) to signals_condition_map = {ConversionSyntax:InvalidOperation,                  DivisionImpossible:InvalidOperation,                  DivisionUndefined:InvalidOperation,                  InvalidContext:InvalidOperation} ##### 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. try:    import threadingexcept ImportError:    # Python was compiled without threads; create a mock object instead    import sys    class MockThreading:        def local(self, sys=sys):            return sys.modules[__name__]    threading = MockThreading()    del sys, MockThreading try:    threading.local except AttributeError:     #To fix reloading, force it to create a new context    #Old contexts have different exceptions in their dicts, making problems.    if hasattr(threading.currentThread(), '__decimal_context__'):        del threading.currentThread().__decimal_context__     def setcontext(context):        """Set this thread's context to context."""        if context in (DefaultContext, BasicContext, ExtendedContext):            context = context.copy()            context.clear_flags()        threading.currentThread().__decimal_context__ = context     def getcontext():        """Returns this thread's context.         If this thread does not yet have a context, returns        a new context and sets this thread's context.        New contexts are copies of DefaultContext.        """        try:            return threading.currentThread().__decimal_context__        except AttributeError:            context = Context()            threading.currentThread().__decimal_context__ = context            return context else:     local = threading.local()    if hasattr(local, '__decimal_context__'):        del local.__decimal_context__     def getcontext(_local=local):        """Returns this thread's context.         If this thread does not yet have a context, returns        a new context and sets this thread's context.        New contexts are copies of DefaultContext.        """        try:            return _local.__decimal_context__        except AttributeError:            context = Context()            _local.__decimal_context__ = context            return context     def setcontext(context, _local=local):        """Set this thread's context to context."""        if context in (DefaultContext, BasicContext, ExtendedContext):            context = context.copy()            context.clear_flags()        _local.__decimal_context__ = context     del threading, local        # Don't contaminate the namespace def localcontext(ctx=None):    """Return a context manager for a copy of the supplied context     Uses a copy of the current context if no context is specified    The returned context manager creates a local decimal context    in a with statement:        def sin(x):             with localcontext() as ctx:                 ctx.prec += 2                 # Rest of sin calculation algorithm                 # uses a precision 2 greater than normal             return +s # Convert result to normal precision          def sin(x):             with localcontext(ExtendedContext):                 # Rest of sin calculation algorithm                 # uses the Extended Context from the                 # General Decimal Arithmetic Specification             return +s # Convert result to normal context     """    # The string below can't be included in the docstring until Python 2.6    # as the doctest module doesn't understand __future__ statements    """    >>> from __future__ import with_statement    >>> print getcontext().prec    28    >>> with localcontext():    ...     ctx = getcontext()    ...     ctx.prec += 2    ...     print ctx.prec    ...    30    >>> with localcontext(ExtendedContext):    ...     print getcontext().prec    ...    9    >>> print getcontext().prec    28    """

Lines 1–500 of 3137.