[Python-checkins] cpython: Issue #7652: Integrate the decimal floating point libmpdec library to speed
Andrew Svetlov
andrew.svetlov at gmail.com
Wed Mar 21 18:49:18 CET 2012
Excellent!
On Wed, Mar 21, 2012 at 7:27 PM, stefan.krah <python-checkins at python.org> wrote:
> http://hg.python.org/cpython/rev/7355550d5357
> changeset: 75850:7355550d5357
> user: Stefan Krah <skrah at bytereef.org>
> date: Wed Mar 21 18:25:23 2012 +0100
> summary:
> Issue #7652: Integrate the decimal floating point libmpdec library to speed
> up the decimal module. Performance gains of the new C implementation are
> between 12x and 80x, depending on the application.
>> files:
> Doc/library/decimal.rst | 178 +-
> Doc/library/numeric.rst | 6 +-
> Doc/whatsnew/3.3.rst | 87 +
> Include/longintrepr.h | 2 +-
> Lib/decimal.py | 196 +-
> Lib/test/support.py | 4 +-
> Lib/test/test_decimal.py | 4550 ++++-
> Lib/test/test_fractions.py | 12 +-
> Lib/test/test_numeric_tower.py | 2 +-
> Misc/NEWS | 4 +
> Misc/valgrind-python.supp | 13 +
> Modules/_decimal/ISSUES.txt | 56 +
> Modules/_decimal/README.txt | 46 +
> Modules/_decimal/_decimal.c | 5512 +++++++
> Modules/_decimal/docstrings.h | 753 +
> Modules/_decimal/libmpdec/README.txt | 90 +
> Modules/_decimal/libmpdec/basearith.c | 635 +
> Modules/_decimal/libmpdec/basearith.h | 213 +
> Modules/_decimal/libmpdec/bits.h | 192 +
> Modules/_decimal/libmpdec/constants.c | 132 +
> Modules/_decimal/libmpdec/constants.h | 83 +
> Modules/_decimal/libmpdec/context.c | 286 +
> Modules/_decimal/libmpdec/convolute.c | 174 +
> Modules/_decimal/libmpdec/convolute.h | 43 +
> Modules/_decimal/libmpdec/crt.c | 179 +
> Modules/_decimal/libmpdec/crt.h | 40 +
> Modules/_decimal/libmpdec/difradix2.c | 173 +
> Modules/_decimal/libmpdec/difradix2.h | 41 +
> Modules/_decimal/libmpdec/fnt.c | 81 +
> Modules/_decimal/libmpdec/fnt.h | 42 +
> Modules/_decimal/libmpdec/fourstep.c | 255 +
> Modules/_decimal/libmpdec/fourstep.h | 41 +
> Modules/_decimal/libmpdec/io.c | 1575 ++
> Modules/_decimal/libmpdec/io.h | 59 +
> Modules/_decimal/libmpdec/literature/REFERENCES.txt | 51 +
> Modules/_decimal/libmpdec/literature/bignum.txt | 83 +
> Modules/_decimal/libmpdec/literature/fnt.py | 208 +
> Modules/_decimal/libmpdec/literature/matrix-transform.txt | 256 +
> Modules/_decimal/libmpdec/literature/mulmod-64.txt | 127 +
> Modules/_decimal/libmpdec/literature/mulmod-ppro.txt | 269 +
> Modules/_decimal/libmpdec/literature/six-step.txt | 63 +
> Modules/_decimal/libmpdec/literature/umodarith.lisp | 692 +
> Modules/_decimal/libmpdec/memory.c | 292 +
> Modules/_decimal/libmpdec/memory.h | 44 +
> Modules/_decimal/libmpdec/mpdecimal.c | 7596 ++++++++++
> Modules/_decimal/libmpdec/mpdecimal.h | 800 +
> Modules/_decimal/libmpdec/numbertheory.c | 132 +
> Modules/_decimal/libmpdec/numbertheory.h | 71 +
> Modules/_decimal/libmpdec/sixstep.c | 212 +
> Modules/_decimal/libmpdec/sixstep.h | 41 +
> Modules/_decimal/libmpdec/transpose.c | 276 +
> Modules/_decimal/libmpdec/transpose.h | 55 +
> Modules/_decimal/libmpdec/typearith.h | 669 +
> Modules/_decimal/libmpdec/umodarith.h | 650 +
> Modules/_decimal/libmpdec/vccompat.h | 62 +
> Modules/_decimal/libmpdec/vcdiv64.asm | 48 +
> Modules/_decimal/libmpdec/vcstdint.h | 232 +
> Modules/_decimal/tests/README.txt | 15 +
> Modules/_decimal/tests/bench.py | 116 +
> Modules/_decimal/tests/deccheck.py | 1074 +
> Modules/_decimal/tests/formathelper.py | 344 +
> Modules/_decimal/tests/randdec.py | 559 +
> Modules/_decimal/tests/randfloat.py | 250 +
> Modules/_decimal/tests/runall-memorydebugger.sh | 175 +
> Modules/_decimal/tests/runall.bat | 121 +
> PCbuild/_decimal.vcproj | 743 +
> PCbuild/pcbuild.sln | 21 +
> configure | 171 +
> configure.ac | 100 +
> pyconfig.h.in | 13 +
> setup.py | 113 +
> 71 files changed, 31440 insertions(+), 1059 deletions(-)
>>> diff --git a/Doc/library/decimal.rst b/Doc/library/decimal.rst
> --- a/Doc/library/decimal.rst
> +++ b/Doc/library/decimal.rst
> @@ -9,6 +9,7 @@
> .. moduleauthor:: Raymond Hettinger <python at rcn.com>
> .. moduleauthor:: Aahz <aahz at pobox.com>
> .. moduleauthor:: Tim Peters <tim.one at comcast.net>
> +.. moduleauthor:: Stefan Krah <skrah at bytereef.org>
> .. sectionauthor:: Raymond D. Hettinger <python at rcn.com>
>> .. import modules for testing inline doctests with the Sphinx doctest builder
> @@ -20,8 +21,9 @@
> # make sure each group gets a fresh context
> setcontext(Context())
>> -The :mod:`decimal` module provides support for decimal floating point
> -arithmetic. It offers several advantages over the :class:`float` datatype:
> +The :mod:`decimal` module provides support for fast correctly-rounded
> +decimal floating point arithmetic. It offers several advantages over the
> +:class:`float` datatype:
>> * Decimal "is based on a floating-point model which was designed with people
> in mind, and necessarily has a paramount guiding principle -- computers must
> @@ -92,7 +94,7 @@
> considered as informational, or treated as exceptions. The signals in the
> decimal module are: :const:`Clamped`, :const:`InvalidOperation`,
> :const:`DivisionByZero`, :const:`Inexact`, :const:`Rounded`, :const:`Subnormal`,
> -:const:`Overflow`, and :const:`Underflow`.
> +:const:`Overflow`, :const:`Underflow` and :const:`FloatOperation`.
>> For each signal there is a flag and a trap enabler. When a signal is
> encountered, its flag is set to one, then, if the trap enabler is
> @@ -122,7 +124,7 @@
>> >>> from decimal import *
> >>> getcontext()
> - Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999,
> + Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999,
> capitals=1, clamp=0, flags=[], traps=[Overflow, DivisionByZero,
> InvalidOperation])
>> @@ -132,7 +134,7 @@
> Construction from an integer or a float performs an exact conversion of the
> value of that integer or float. Decimal numbers include special values such as
> :const:`NaN` which stands for "Not a number", positive and negative
> -:const:`Infinity`, and :const:`-0`.
> +:const:`Infinity`, and :const:`-0`::
>> >>> getcontext().prec = 28
> >>> Decimal(10)
> @@ -152,6 +154,25 @@
> >>> Decimal('-Infinity')
> Decimal('-Infinity')
>> +If the :exc:`FloatOperation` signal is trapped, accidental mixing of
> +decimals and floats in constructors or ordering comparisons raises
> +an exception::
> +
> + >>> c = getcontext()
> + >>> c.traps[FloatOperation] = True
> + >>> Decimal(3.14)
> + Traceback (most recent call last):
> + File "<stdin>", line 1, in <module>
> + decimal.FloatOperation: [<class 'decimal.FloatOperation'>]
> + >>> Decimal('3.5') < 3.7
> + Traceback (most recent call last):
> + File "<stdin>", line 1, in <module>
> + decimal.FloatOperation: [<class 'decimal.FloatOperation'>]
> + >>> Decimal('3.5') == 3.5
> + True
> +
> +.. versionadded:: 3.3
> +
> The significance of a new Decimal is determined solely by the number of digits
> input. Context precision and rounding only come into play during arithmetic
> operations.
> @@ -169,6 +190,16 @@
> >>> Decimal('3.1415926535') + Decimal('2.7182818285')
> Decimal('5.85988')
>> +If the internal limits of the C version are exceeded, constructing
> +a decimal raises :class:`InvalidOperation`::
> +
> + >>> Decimal("1e9999999999999999999")
> + Traceback (most recent call last):
> + File "<stdin>", line 1, in <module>
> + decimal.InvalidOperation: [<class 'decimal.InvalidOperation'>]
> +
> +.. versionchanged:: 3.3
> +
> Decimals interact well with much of the rest of Python. Here is a small decimal
> floating point flying circus:
>> @@ -244,7 +275,7 @@
> Decimal('0.142857142857142857142857142857142857142857142857142857142857')
>> >>> ExtendedContext
> - Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999,
> + Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999,
> capitals=1, clamp=0, flags=[], traps=[])
> >>> setcontext(ExtendedContext)
> >>> Decimal(1) / Decimal(7)
> @@ -269,7 +300,7 @@
> >>> Decimal(355) / Decimal(113)
> Decimal('3.14159292')
> >>> getcontext()
> - Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999,
> + Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999,
> capitals=1, clamp=0, flags=[Inexact, Rounded], traps=[])
>> The *flags* entry shows that the rational approximation to :const:`Pi` was
> @@ -358,6 +389,10 @@
> The argument to the constructor is now permitted to be a :class:`float`
> instance.
>> + .. versionchanged:: 3.3
> + :class:`float` arguments raise an exception if the :exc:`FloatOperation`
> + trap is set. By default the trap is off.
> +
> Decimal floating point objects share many properties with the other built-in
> numeric types such as :class:`float` and :class:`int`. All of the usual math
> operations and special methods apply. Likewise, decimal objects can be
> @@ -880,39 +915,33 @@
> In single threaded environments, it is preferable to not use this context at
> all. Instead, simply create contexts explicitly as described below.
>> - The default values are precision=28, rounding=ROUND_HALF_EVEN, and enabled traps
> - for Overflow, InvalidOperation, and DivisionByZero.
> + The default values are :attr:`prec`\ =\ :const:`28`,
> + :attr:`rounding`\ =\ :const:`ROUND_HALF_EVEN`,
> + and enabled traps for :class:`Overflow`, :class:`InvalidOperation`, and
> + :class:`DivisionByZero`.
>> In addition to the three supplied contexts, new contexts can be created with the
> :class:`Context` constructor.
>>> -.. class:: Context(prec=None, rounding=None, traps=None, flags=None, Emin=None, Emax=None, capitals=None, clamp=None)
> +.. class:: Context(prec=None, rounding=None, Emin=None, Emax=None, capitals=None, clamp=None, flags=None, traps=None)
>> Creates a new context. If a field is not specified or is :const:`None`, the
> default values are copied from the :const:`DefaultContext`. If the *flags*
> field is not specified or is :const:`None`, all flags are cleared.
>> - The *prec* field is a positive integer that sets the precision for arithmetic
> - operations in the context.
> -
> - The *rounding* option is one of:
> -
> - * :const:`ROUND_CEILING` (towards :const:`Infinity`),
> - * :const:`ROUND_DOWN` (towards zero),
> - * :const:`ROUND_FLOOR` (towards :const:`-Infinity`),
> - * :const:`ROUND_HALF_DOWN` (to nearest with ties going towards zero),
> - * :const:`ROUND_HALF_EVEN` (to nearest with ties going to nearest even integer),
> - * :const:`ROUND_HALF_UP` (to nearest with ties going away from zero), or
> - * :const:`ROUND_UP` (away from zero).
> - * :const:`ROUND_05UP` (away from zero if last digit after rounding towards zero
> - would have been 0 or 5; otherwise towards zero)
> + *prec* is an integer in the range [:const:`1`, :const:`MAX_PREC`] that sets
> + the precision for arithmetic operations in the context.
> +
> + The *rounding* option is one of the constants listed in the section
> + `Rounding Modes`_.
>> The *traps* and *flags* fields list any signals to be set. Generally, new
> contexts should only set traps and leave the flags clear.
>> The *Emin* and *Emax* fields are integers specifying the outer limits allowable
> - for exponents.
> + for exponents. *Emin* must be in the range [:const:`MIN_EMIN`, :const:`0`],
> + *Emax* in the range [:const:`0`, :const:`MAX_EMAX`].
>> The *capitals* field is either :const:`0` or :const:`1` (the default). If set to
> :const:`1`, exponents are printed with a capital :const:`E`; otherwise, a
> @@ -951,6 +980,12 @@
>> Resets all of the flags to :const:`0`.
>> + .. method:: clear_traps()
> +
> + Resets all of the traps to :const:`0`.
> +
> + .. versionadded:: 3.3
> +
> .. method:: copy()
>> Return a duplicate of the context.
> @@ -1250,8 +1285,13 @@
> With two arguments, compute ``x**y``. If ``x`` is negative then ``y``
> must be integral. The result will be inexact unless ``y`` is integral and
> the result is finite and can be expressed exactly in 'precision' digits.
> - The result should always be correctly rounded, using the rounding mode of
> - the current thread's context.
> + The rounding mode of the context is used. Results are always correctly-rounded
> + in the Python version.
> +
> + .. versionchanged:: 3.3
> + The C module computes :meth:`power` in terms of the correctly-rounded
> + :meth:`exp` and :meth:`ln` functions. The result is well-defined but
> + only "almost always correctly-rounded".
>> With three arguments, compute ``(x**y) % modulo``. For the three argument
> form, the following restrictions on the arguments hold:
> @@ -1339,6 +1379,66 @@
>> .. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
>> +.. _decimal-rounding-modes:
> +
> +Constants
> +---------
> +
> +The constants in this section are only relevant for the C module. They
> +are also included in the pure Python version for compatibility.
> +
> ++--------------------+---------------------+------------------------------+
> +| | 32-bit | 64-bit |
> ++====================+=====================+==============================+
> +| .. data:: MAX_PREC | :const:`425000000` | :const:`999999999999999999` |
> ++--------------------+---------------------+------------------------------+
> +| .. data:: MAX_EMAX | :const:`425000000` | :const:`999999999999999999` |
> ++--------------------+---------------------+------------------------------+
> +| .. data:: MIN_EMIN | :const:`-425000000` | :const:`-999999999999999999` |
> ++--------------------+---------------------+------------------------------+
> +
> +.. data:: HAVE_THREADS
> +
> + The default value is True. If Python is compiled without threads, the
> + C version automatically disables the expensive thread local context
> + machinery. In this case, the value is False.
> +
> +Rounding modes
> +--------------
> +
> +.. data:: ROUND_CEILING
> +
> + Round towards :const:`Infinity`.
> +
> +.. data:: ROUND_DOWN
> +
> + Round towards zero.
> +
> +.. data:: ROUND_FLOOR
> +
> + Round towards :const:`-Infinity`.
> +
> +.. data:: ROUND_HALF_DOWN
> +
> + Round to nearest with ties going towards zero.
> +
> +.. data:: ROUND_HALF_EVEN
> +
> + Round to nearest with ties going to nearest even integer.
> +
> +.. data:: ROUND_HALF_UP
> +
> + Round to nearest with ties going away from zero.
> +
> +.. data:: ROUND_UP
> +
> + Round away from zero.
> +
> +.. data:: ROUND_05UP
> +
> + Round away from zero if last digit after rounding towards zero would have
> + been 0 or 5; otherwise round towards zero.
> +
>> .. _decimal-signals:
>> @@ -1403,7 +1503,6 @@
> Infinity / Infinity
> x % 0
> Infinity % x
> - x._rescale( non-integer )
> sqrt(-x) and x > 0
> 0 ** 0
> x ** (non-integer)
> @@ -1446,6 +1545,23 @@
> Occurs when a subnormal result is pushed to zero by rounding. :class:`Inexact`
> and :class:`Subnormal` are also signaled.
>> +
> +.. class:: FloatOperation
> +
> + Enable stricter semantics for mixing floats and Decimals.
> +
> + If the signal is not trapped (default), mixing floats and Decimals is
> + permitted in the :class:`~decimal.Decimal` constructor,
> + :meth:`~decimal.Context.create_decimal` and all comparison operators.
> + Both conversion and comparisons are exact. Any occurrence of a mixed
> + operation is silently recorded by setting :exc:`FloatOperation` in the
> + context flags. Explicit conversions with :meth:`~decimal.Decimal.from_float`
> + or :meth:`~decimal.Context.create_decimal_from_float` do not set the flag.
> +
> + Otherwise (the signal is trapped), only equality comparisons and explicit
> + conversions are silent. All other mixed operations raise :exc:`FloatOperation`.
> +
> +
> The following table summarizes the hierarchy of signals::
>> exceptions.ArithmeticError(exceptions.Exception)
> @@ -1458,10 +1574,12 @@
> InvalidOperation
> Rounded
> Subnormal
> + FloatOperation
>> .. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
>>> +
> .. _decimal-notes:
>> Floating Point Notes
> @@ -1571,7 +1689,7 @@
> the following calculation returns a value equal to zero:
>> >>> 1 / Decimal('Infinity')
> - Decimal('0E-1000000026')
> + Decimal('0E-1000026')
>> .. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
>> @@ -1583,7 +1701,7 @@
>> The :func:`getcontext` function accesses a different :class:`Context` object for
> each thread. Having separate thread contexts means that threads may make
> -changes (such as ``getcontext.prec=10``) without interfering with other threads.
> +changes (such as ``getcontext().prec=10``) without interfering with other threads.
>> Likewise, the :func:`setcontext` function automatically assigns its target to
> the current thread.
> diff --git a/Doc/library/numeric.rst b/Doc/library/numeric.rst
> --- a/Doc/library/numeric.rst
> +++ b/Doc/library/numeric.rst
> @@ -8,9 +8,9 @@
> The modules described in this chapter provide numeric and math-related functions
> and data types. The :mod:`numbers` module defines an abstract hierarchy of
> numeric types. The :mod:`math` and :mod:`cmath` modules contain various
> -mathematical functions for floating-point and complex numbers. For users more
> -interested in decimal accuracy than in speed, the :mod:`decimal` module supports
> -exact representations of decimal numbers.
> +mathematical functions for floating-point and complex numbers. The :mod:`decimal`
> +module supports exact representations of decimal numbers, using arbitrary precision
> +arithmetic.
>> The following modules are documented in this chapter:
>> diff --git a/Doc/whatsnew/3.3.rst b/Doc/whatsnew/3.3.rst
> --- a/Doc/whatsnew/3.3.rst
> +++ b/Doc/whatsnew/3.3.rst
> @@ -596,6 +596,93 @@
>> (Contributed by Iñigo Serna in :issue:`6755`)
>> +decimal
> +-------
> +
> +:issue:`7652` - integrate fast native decimal arithmetic.
> + C-module and libmpdec written by Stefan Krah.
> +
> +The new C version of the decimal module integrates the high speed libmpdec
> +library for arbitrary precision correctly-rounded decimal arithmetic.
> +libmpdec conforms to IBM's General Decimal Arithmetic Specification.
> +
> +Performance gains range from 12x for database applications to 80x for
> +numerically intensive applications:
> +
> + +---------+-------------+--------------+-------------+
> + | | decimal.py | _decimal | speedup |
> + +=========+=============+==============+=============+
> + | pi | 42.75s | 0.58s | 74x |
> + +---------+-------------+--------------+-------------+
> + | telco | 172.19s | 5.68s | 30x |
> + +---------+-------------+--------------+-------------+
> + | psycopg | 3.57s | 0.29s | 12x |
> + +---------+-------------+--------------+-------------+
> +
> +Features
> +~~~~~~~~
> +
> +* The :exc:`~decimal.FloatOperation` signal optionally enables stricter
> + semantics for mixing floats and Decimals.
> +
> +* If Python is compiled without threads, the C version automatically
> + disables the expensive thread local context machinery. In this case,
> + the variable :data:`~decimal.HAVE_THREADS` is set to False.
> +
> +API changes
> +~~~~~~~~~~~
> +
> +* The C module has the following context limits, depending on the machine
> + architecture:
> +
> + +-------------------+---------------------+------------------------------+
> + | | 32-bit | 64-bit |
> + +===================+=====================+==============================+
> + | :const:`MAX_PREC` | :const:`425000000` | :const:`999999999999999999` |
> + +-------------------+---------------------+------------------------------+
> + | :const:`MAX_EMAX` | :const:`425000000` | :const:`999999999999999999` |
> + +-------------------+---------------------+------------------------------+
> + | :const:`MIN_EMIN` | :const:`-425000000` | :const:`-999999999999999999` |
> + +-------------------+---------------------+------------------------------+
> +
> +* In the context templates (:class:`~decimal.DefaultContext`,
> + :class:`~decimal.BasicContext` and :class:`~decimal.ExtendedContext`)
> + the magnitude of :attr:`~decimal.Context.Emax` and
> + :attr:`~decimal.Context.Emin` has changed to :const:`999999`.
> +
> +* The :class:`~decimal.Decimal` constructor in decimal.py does not observe
> + the context limits and converts values with arbitrary exponents or precision
> + exactly. Since the C version has internal limits, the following scheme is
> + used: If possible, values are converted exactly, otherwise
> + :exc:`~decimal.InvalidOperation` is raised and the result is NaN. In the
> + latter case it is always possible to use :meth:`~decimal.Context.create_decimal`
> + in order to obtain a rounded or inexact value.
> +
> +
> +* The power function in decimal.py is always correctly-rounded. In the
> + C version, it is defined in terms of the correctly-rounded
> + :meth:`~decimal.Decimal.exp` and :meth:`~decimal.Decimal.ln` functions,
> + but the final result is only "almost always correctly rounded".
> +
> +
> +* In the C version, the context dictionary containing the signals is a
> + :class:`~collections.abc.MutableMapping`. For speed reasons,
> + :attr:`~decimal.Context.flags` and :attr:`~decimal.Context.traps` always
> + refer to the same :class:`~collections.abc.MutableMapping` that the context
> + was initialized with. If a new signal dictionary is assigned,
> + :attr:`~decimal.Context.flags` and :attr:`~decimal.Context.traps`
> + are updated with the new values, but they do not reference the RHS
> + dictionary.
> +
> +
> +* Pickling a :class:`~decimal.Context` produces a different output in order
> + to have a common interchange format for the Python and C versions.
> +
> +
> +* The order of arguments in the :class:`~decimal.Context` constructor has been
> + changed to match the order displayed by :func:`repr`.
> +
> +
> faulthandler
> ------------
>> diff --git a/Include/longintrepr.h b/Include/longintrepr.h
> --- a/Include/longintrepr.h
> +++ b/Include/longintrepr.h
> @@ -6,7 +6,7 @@
> #endif
>>> -/* This is published for the benefit of "friend" marshal.c only. */
> +/* This is published for the benefit of "friends" marshal.c and _decimal.c. */
>> /* Parameters of the long integer representation. There are two different
> sets of parameters: one set for 30-bit digits, stored in an unsigned 32-bit
> diff --git a/Lib/decimal.py b/Lib/decimal.py
> --- a/Lib/decimal.py
> +++ b/Lib/decimal.py
> @@ -46,8 +46,8 @@
> Decimal('-0.0123')
> >>> Decimal(123456)
> Decimal('123456')
> ->>> Decimal('123.45e12345678901234567890')
> -Decimal('1.2345E+12345678901234567892')
> +>>> Decimal('123.45e12345678')
> +Decimal('1.2345E+12345680')
> >>> Decimal('1.33') + Decimal('1.27')
> Decimal('2.60')
> >>> Decimal('12.34') + Decimal('3.87') - Decimal('18.41')
> @@ -122,13 +122,20 @@
> # Exceptions
> 'DecimalException', 'Clamped', 'InvalidOperation', 'DivisionByZero',
> 'Inexact', 'Rounded', 'Subnormal', 'Overflow', 'Underflow',
> + 'FloatOperation',
>> # Constants for use in setting up contexts
> 'ROUND_DOWN', 'ROUND_HALF_UP', 'ROUND_HALF_EVEN', 'ROUND_CEILING',
> 'ROUND_FLOOR', 'ROUND_UP', 'ROUND_HALF_DOWN', 'ROUND_05UP',
>> # Functions for manipulating contexts
> - 'setcontext', 'getcontext', 'localcontext'
> + 'setcontext', 'getcontext', 'localcontext',
> +
> + # Limits for the C version for compatibility
> + 'MAX_PREC', 'MAX_EMAX', 'MIN_EMIN', 'MIN_ETINY',
> +
> + # C version: compile time choice that enables the thread local context
> + 'HAVE_THREADS'
> ]
>> __version__ = '1.70' # Highest version of the spec this complies with
> @@ -137,6 +144,7 @@
> import copy as _copy
> import math as _math
> import numbers as _numbers
> +import sys
>> try:
> from collections import namedtuple as _namedtuple
> @@ -154,6 +162,19 @@
> ROUND_HALF_DOWN = 'ROUND_HALF_DOWN'
> ROUND_05UP = 'ROUND_05UP'
>> +# Compatibility with the C version
> +HAVE_THREADS = True
> +if sys.maxsize == 2**63-1:
> + MAX_PREC = 999999999999999999
> + MAX_EMAX = 999999999999999999
> + MIN_EMIN = -999999999999999999
> +else:
> + MAX_PREC = 425000000
> + MAX_EMAX = 425000000
> + MIN_EMIN = -425000000
> +
> +MIN_ETINY = MIN_EMIN - (MAX_PREC-1)
> +
> # Errors
>> class DecimalException(ArithmeticError):
> @@ -370,9 +391,24 @@
> In all cases, Inexact, Rounded, and Subnormal will also be raised.
> """
>> +class FloatOperation(DecimalException):
> + """Enable stricter semantics for mixing floats and Decimals.
> +
> + If the signal is not trapped (default), mixing floats and Decimals is
> + permitted in the Decimal() constructor, context.create_decimal() and
> + all comparison operators. Both conversion and comparisons are exact.
> + Any occurrence of a mixed operation is silently recorded by setting
> + FloatOperation in the context flags. Explicit conversions with
> + Decimal.from_float() or context.create_decimal_from_float() do not
> + set the flag.
> +
> + Otherwise (the signal is trapped), only equality comparisons and explicit
> + conversions are silent. All other mixed operations raise FloatOperation.
> + """
> +
> # List of public traps and flags
> _signals = [Clamped, DivisionByZero, Inexact, Overflow, Rounded,
> - Underflow, InvalidOperation, Subnormal]
> + Underflow, InvalidOperation, Subnormal, FloatOperation]
>> # Map conditions (per the spec) to signals
> _condition_map = {ConversionSyntax:InvalidOperation,
> @@ -380,6 +416,10 @@
> DivisionUndefined:InvalidOperation,
> InvalidContext:InvalidOperation}
>> +# Valid rounding modes
> +_rounding_modes = (ROUND_DOWN, ROUND_HALF_UP, ROUND_HALF_EVEN, ROUND_CEILING,
> + ROUND_FLOOR, ROUND_UP, ROUND_HALF_DOWN, ROUND_05UP)
> +
> ##### Context Functions ##################################################
>> # The getcontext() and setcontext() function manage access to a thread-local
> @@ -392,12 +432,11 @@
> import threading
> except ImportError:
> # Python was compiled without threads; create a mock object instead
> - import sys
> class MockThreading(object):
> def local(self, sys=sys):
> return sys.modules[__name__]
> threading = MockThreading()
> - del sys, MockThreading
> + del MockThreading
>> try:
> threading.local
> @@ -650,6 +689,11 @@
> return self
>> if isinstance(value, float):
> + if context is None:
> + context = getcontext()
> + context._raise_error(FloatOperation,
> + "strict semantics for mixing floats and Decimals are "
> + "enabled")
> value = Decimal.from_float(value)
> self._exp = value._exp
> self._sign = value._sign
> @@ -684,7 +728,9 @@
> """
> if isinstance(f, int): # handle integer inputs
> return cls(f)
> - if _math.isinf(f) or _math.isnan(f): # raises TypeError if not a float
> + if not isinstance(f, float):
> + raise TypeError("argument must be int or float.")
> + if _math.isinf(f) or _math.isnan(f):
> return cls(repr(f))
> if _math.copysign(1.0, f) == 1.0:
> sign = 0
> @@ -1906,11 +1952,12 @@
> def _power_modulo(self, other, modulo, context=None):
> """Three argument version of __pow__"""
>> - # if can't convert other and modulo to Decimal, raise
> - # TypeError; there's no point returning NotImplemented (no
> - # equivalent of __rpow__ for three argument pow)
> - other = _convert_other(other, raiseit=True)
> - modulo = _convert_other(modulo, raiseit=True)
> + other = _convert_other(other)
> + if other is NotImplemented:
> + return other
> + modulo = _convert_other(modulo)
> + if modulo is NotImplemented:
> + return modulo
>> if context is None:
> context = getcontext()
> @@ -3832,11 +3879,9 @@
> clamp - If 1, change exponents if too high (Default 0)
> """
>> - def __init__(self, prec=None, rounding=None,
> - traps=None, flags=None,
> - Emin=None, Emax=None,
> - capitals=None, clamp=None,
> - _ignored_flags=None):
> + def __init__(self, prec=None, rounding=None, Emin=None, Emax=None,
> + capitals=None, clamp=None, flags=None, traps=None,
> + _ignored_flags=None):
> # Set defaults; for everything except flags and _ignored_flags,
> # inherit from DefaultContext.
> try:
> @@ -3859,17 +3904,78 @@
> if traps is None:
> self.traps = dc.traps.copy()
> elif not isinstance(traps, dict):
> - self.traps = dict((s, int(s in traps)) for s in _signals)
> + self.traps = dict((s, int(s in traps)) for s in _signals + traps)
> else:
> self.traps = traps
>> if flags is None:
> self.flags = dict.fromkeys(_signals, 0)
> elif not isinstance(flags, dict):
> - self.flags = dict((s, int(s in flags)) for s in _signals)
> + self.flags = dict((s, int(s in flags)) for s in _signals + flags)
> else:
> self.flags = flags
>> + def _set_integer_check(self, name, value, vmin, vmax):
> + if not isinstance(value, int):
> + raise TypeError("%s must be an integer" % name)
> + if vmin == '-inf':
> + if value > vmax:
> + raise ValueError("%s must be in [%s, %d]. got: %s" % (name, vmin, vmax, value))
> + elif vmax == 'inf':
> + if value < vmin:
> + raise ValueError("%s must be in [%d, %s]. got: %s" % (name, vmin, vmax, value))
> + else:
> + if value < vmin or value > vmax:
> + raise ValueError("%s must be in [%d, %d]. got %s" % (name, vmin, vmax, value))
> + return object.__setattr__(self, name, value)
> +
> + def _set_signal_dict(self, name, d):
> + if not isinstance(d, dict):
> + raise TypeError("%s must be a signal dict" % d)
> + for key in d:
> + if not key in _signals:
> + raise KeyError("%s is not a valid signal dict" % d)
> + for key in _signals:
> + if not key in d:
> + raise KeyError("%s is not a valid signal dict" % d)
> + return object.__setattr__(self, name, d)
> +
> + def __setattr__(self, name, value):
> + if name == 'prec':
> + return self._set_integer_check(name, value, 1, 'inf')
> + elif name == 'Emin':
> + return self._set_integer_check(name, value, '-inf', 0)
> + elif name == 'Emax':
> + return self._set_integer_check(name, value, 0, 'inf')
> + elif name == 'capitals':
> + return self._set_integer_check(name, value, 0, 1)
> + elif name == 'clamp':
> + return self._set_integer_check(name, value, 0, 1)
> + elif name == 'rounding':
> + if not value in _rounding_modes:
> + # raise TypeError even for strings to have consistency
> + # among various implementations.
> + raise TypeError("%s: invalid rounding mode" % value)
> + return object.__setattr__(self, name, value)
> + elif name == 'flags' or name == 'traps':
> + return self._set_signal_dict(name, value)
> + elif name == '_ignored_flags':
> + return object.__setattr__(self, name, value)
> + else:
> + raise AttributeError(
> + "'decimal.Context' object has no attribute '%s'" % name)
> +
> + def __delattr__(self, name):
> + raise AttributeError("%s cannot be deleted" % name)
> +
> + # Support for pickling, copy, and deepcopy
> + def __reduce__(self):
> + flags = [sig for sig, v in self.flags.items() if v]
> + traps = [sig for sig, v in self.traps.items() if v]
> + return (self.__class__,
> + (self.prec, self.rounding, self.Emin, self.Emax,
> + self.capitals, self.clamp, flags, traps))
> +
> def __repr__(self):
> """Show the current context."""
> s = []
> @@ -3888,18 +3994,24 @@
> for flag in self.flags:
> self.flags[flag] = 0
>> + def clear_traps(self):
> + """Reset all traps to zero"""
> + for flag in self.traps:
> + self.traps[flag] = 0
> +
> def _shallow_copy(self):
> """Returns a shallow copy from self."""
> - nc = Context(self.prec, self.rounding, self.traps,
> - self.flags, self.Emin, self.Emax,
> - self.capitals, self.clamp, self._ignored_flags)
> + nc = Context(self.prec, self.rounding, self.Emin, self.Emax,
> + self.capitals, self.clamp, self.flags, self.traps,
> + self._ignored_flags)
> return nc
>> def copy(self):
> """Returns a deep copy from self."""
> - nc = Context(self.prec, self.rounding, self.traps.copy(),
> - self.flags.copy(), self.Emin, self.Emax,
> - self.capitals, self.clamp, self._ignored_flags)
> + nc = Context(self.prec, self.rounding, self.Emin, self.Emax,
> + self.capitals, self.clamp,
> + self.flags.copy(), self.traps.copy(),
> + self._ignored_flags)
> return nc
> __copy__ = copy
>> @@ -4062,6 +4174,8 @@
> >>> ExtendedContext.canonical(Decimal('2.50'))
> Decimal('2.50')
> """
> + if not isinstance(a, Decimal):
> + raise TypeError("canonical requires a Decimal as an argument.")
> return a.canonical(context=self)
>> def compare(self, a, b):
> @@ -4372,6 +4486,8 @@
> >>> ExtendedContext.is_canonical(Decimal('2.50'))
> True
> """
> + if not isinstance(a, Decimal):
> + raise TypeError("is_canonical requires a Decimal as an argument.")
> return a.is_canonical()
>> def is_finite(self, a):
> @@ -4964,7 +5080,7 @@
> +Normal
> +Infinity
>> - >>> c = Context(ExtendedContext)
> + >>> c = ExtendedContext.copy()
> >>> c.Emin = -999
> >>> c.Emax = 999
> >>> c.number_class(Decimal('Infinity'))
> @@ -5916,6 +6032,12 @@
> if equality_op and isinstance(other, _numbers.Complex) and other.imag == 0:
> other = other.real
> if isinstance(other, float):
> + context = getcontext()
> + if equality_op:
> + context.flags[FloatOperation] = 1
> + else:
> + context._raise_error(FloatOperation,
> + "strict semantics for mixing floats and Decimals are enabled")
> return self, Decimal.from_float(other)
> return NotImplemented, NotImplemented
>> @@ -5929,8 +6051,8 @@
> prec=28, rounding=ROUND_HALF_EVEN,
> traps=[DivisionByZero, Overflow, InvalidOperation],
> flags=[],
> - Emax=999999999,
> - Emin=-999999999,
> + Emax=999999,
> + Emin=-999999,
> capitals=1,
> clamp=0
> )
> @@ -6080,7 +6202,7 @@
> # if format type is 'g' or 'G' then a precision of 0 makes little
> # sense; convert it to 1. Same if format type is unspecified.
> if format_dict['precision'] == 0:
> - if format_dict['type'] is None or format_dict['type'] in 'gG':
> + if format_dict['type'] is None or format_dict['type'] in 'gGn':
> format_dict['precision'] = 1
>> # determine thousands separator, grouping, and decimal separator, and
> @@ -6254,16 +6376,26 @@
>> # Constants related to the hash implementation; hash(x) is based
> # on the reduction of x modulo _PyHASH_MODULUS
> -import sys
> _PyHASH_MODULUS = sys.hash_info.modulus
> # hash values to use for positive and negative infinities, and nans
> _PyHASH_INF = sys.hash_info.inf
> _PyHASH_NAN = sys.hash_info.nan
> -del sys
>> # _PyHASH_10INV is the inverse of 10 modulo the prime _PyHASH_MODULUS
> _PyHASH_10INV = pow(10, _PyHASH_MODULUS - 2, _PyHASH_MODULUS)
> -
> +del sys
> +
> +try:
> + import _decimal
> +except ImportError:
> + pass
> +else:
> + s1 = set(dir())
> + s2 = set(dir(_decimal))
> + for name in s1 - s2:
> + del globals()[name]
> + del s1, s2, name
> + from _decimal import *
>> if __name__ == '__main__':
> import doctest, decimal
> diff --git a/Lib/test/support.py b/Lib/test/support.py
> --- a/Lib/test/support.py
> +++ b/Lib/test/support.py
> @@ -1416,7 +1416,7 @@
> #=======================================================================
> # doctest driver.
>> -def run_doctest(module, verbosity=None):
> +def run_doctest(module, verbosity=None, optionflags=0):
> """Run doctest on the given module. Return (#failures, #tests).
>> If optional argument verbosity is not specified (or is None), pass
> @@ -1431,7 +1431,7 @@
> else:
> verbosity = None
>> - f, t = doctest.testmod(module, verbose=verbosity)
> + f, t = doctest.testmod(module, verbose=verbosity, optionflags=optionflags)
> if f:
> raise TestFailed("%d of %d doctests failed" % (f, t))
> if verbose:
> diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py
> --- a/Lib/test/test_decimal.py
> +++ b/Lib/test/test_decimal.py
> @@ -16,7 +16,7 @@
>> Cowlishaw's tests can be downloaded from:
>> - www2.hursley.ibm.com/decimal/dectest.zip
> + http://speleotrove.com/decimal/dectest.zip
>> This test module can be called from command line with one parameter (Arithmetic
> or Behaviour) to test each part, or without parameter to test both parts. If
> @@ -30,37 +30,74 @@
> import warnings
> import pickle, copy
> import unittest
> -from decimal import *
> import numbers
> +import locale
> from test.support import (run_unittest, run_doctest, is_resource_enabled,
> requires_IEEE_754)
> -from test.support import check_warnings
> +from test.support import check_warnings, import_fresh_module, TestFailed
> import random
> +import time
> +import warnings
> try:
> import threading
> except ImportError:
> threading = None
>> +
> +C = import_fresh_module('decimal', fresh=['_decimal'])
> +P = import_fresh_module('decimal', blocked=['_decimal'])
> +orig_sys_decimal = sys.modules['decimal']
> +
> +# fractions module must import the correct decimal module.
> +cfractions = import_fresh_module('fractions', fresh=['fractions'])
> +sys.modules['decimal'] = P
> +pfractions = import_fresh_module('fractions', fresh=['fractions'])
> +sys.modules['decimal'] = C
> +fractions = {C:cfractions, P:pfractions}
> +sys.modules['decimal'] = orig_sys_decimal
> +
> +
> # Useful Test Constant
> -Signals = tuple(getcontext().flags.keys())
> -
> +Signals = {
> + C: tuple(C.getcontext().flags.keys()) if C else None,
> + P: tuple(P.getcontext().flags.keys())
> +}
> # Signals ordered with respect to precedence: when an operation
> # produces multiple signals, signals occurring later in the list
> # should be handled before those occurring earlier in the list.
> -OrderedSignals = (Clamped, Rounded, Inexact, Subnormal,
> - Underflow, Overflow, DivisionByZero, InvalidOperation)
> +OrderedSignals = {
> + C: [C.Clamped, C.Rounded, C.Inexact, C.Subnormal, C.Underflow,
> + C.Overflow, C.DivisionByZero, C.InvalidOperation,
> + C.FloatOperation] if C else None,
> + P: [P.Clamped, P.Rounded, P.Inexact, P.Subnormal, P.Underflow,
> + P.Overflow, P.DivisionByZero, P.InvalidOperation,
> + P.FloatOperation]
> +}
> +def assert_signals(cls, context, attr, expected):
> + d = getattr(context, attr)
> + cls.assertTrue(all(d[s] if s in expected else not d[s] for s in d))
> +
> +RoundingModes = {
> + C: (C.ROUND_UP, C.ROUND_DOWN, C.ROUND_CEILING, C.ROUND_FLOOR,
> + C.ROUND_HALF_UP, C.ROUND_HALF_DOWN, C.ROUND_HALF_EVEN,
> + C.ROUND_05UP) if C else None,
> + P: (P.ROUND_UP, P.ROUND_DOWN, P.ROUND_CEILING, P.ROUND_FLOOR,
> + P.ROUND_HALF_UP, P.ROUND_HALF_DOWN, P.ROUND_HALF_EVEN,
> + P.ROUND_05UP)
> +}
>> # Tests are built around these assumed context defaults.
> # test_main() restores the original context.
> -def init():
> - global ORIGINAL_CONTEXT
> - ORIGINAL_CONTEXT = getcontext().copy()
> - DefaultTestContext = Context(
> - prec = 9,
> - rounding = ROUND_HALF_EVEN,
> - traps = dict.fromkeys(Signals, 0)
> - )
> - setcontext(DefaultTestContext)
> +ORIGINAL_CONTEXT = {
> + C: C.getcontext().copy() if C else None,
> + P: P.getcontext().copy()
> +}
> +def init(m):
> + if not m: return
> + DefaultTestContext = m.Context(
> + prec=9, rounding=m.ROUND_HALF_EVEN, traps=dict.fromkeys(Signals[m], 0)
> + )
> + m.setcontext(DefaultTestContext)
>> TESTDATADIR = 'decimaltestdata'
> if __name__ == '__main__':
> @@ -72,149 +109,175 @@
>> skip_expected = not os.path.isdir(directory)
>> -# list of individual .decTest test ids that correspond to tests that
> -# we're skipping for one reason or another.
> -skipped_test_ids = set([
> - # Skip implementation-specific scaleb tests.
> - 'scbx164',
> - 'scbx165',
> -
> - # For some operations (currently exp, ln, log10, power), the decNumber
> - # reference implementation imposes additional restrictions on the context
> - # and operands. These restrictions are not part of the specification;
> - # however, the effect of these restrictions does show up in some of the
> - # testcases. We skip testcases that violate these restrictions, since
> - # Decimal behaves differently from decNumber for these testcases so these
> - # testcases would otherwise fail.
> - 'expx901',
> - 'expx902',
> - 'expx903',
> - 'expx905',
> - 'lnx901',
> - 'lnx902',
> - 'lnx903',
> - 'lnx905',
> - 'logx901',
> - 'logx902',
> - 'logx903',
> - 'logx905',
> - 'powx1183',
> - 'powx1184',
> - 'powx4001',
> - 'powx4002',
> - 'powx4003',
> - 'powx4005',
> - 'powx4008',
> - 'powx4010',
> - 'powx4012',
> - 'powx4014',
> - ])
> -
> # Make sure it actually raises errors when not expected and caught in flags
> # Slower, since it runs some things several times.
> EXTENDEDERRORTEST = False
>> -#Map the test cases' error names to the actual errors
> -ErrorNames = {'clamped' : Clamped,
> - 'conversion_syntax' : InvalidOperation,
> - 'division_by_zero' : DivisionByZero,
> - 'division_impossible' : InvalidOperation,
> - 'division_undefined' : InvalidOperation,
> - 'inexact' : Inexact,
> - 'invalid_context' : InvalidOperation,
> - 'invalid_operation' : InvalidOperation,
> - 'overflow' : Overflow,
> - 'rounded' : Rounded,
> - 'subnormal' : Subnormal,
> - 'underflow' : Underflow}
> -
> -
> -def Nonfunction(*args):
> - """Doesn't do anything."""
> - return None
> -
> -RoundingDict = {'ceiling' : ROUND_CEILING, #Maps test-case names to roundings.
> - 'down' : ROUND_DOWN,
> - 'floor' : ROUND_FLOOR,
> - 'half_down' : ROUND_HALF_DOWN,
> - 'half_even' : ROUND_HALF_EVEN,
> - 'half_up' : ROUND_HALF_UP,
> - 'up' : ROUND_UP,
> - '05up' : ROUND_05UP}
> -
> -# Name adapter to be able to change the Decimal and Context
> -# interface without changing the test files from Cowlishaw
> -nameAdapter = {'and':'logical_and',
> - 'apply':'_apply',
> - 'class':'number_class',
> - 'comparesig':'compare_signal',
> - 'comparetotal':'compare_total',
> - 'comparetotmag':'compare_total_mag',
> - 'copy':'copy_decimal',
> - 'copyabs':'copy_abs',
> - 'copynegate':'copy_negate',
> - 'copysign':'copy_sign',
> - 'divideint':'divide_int',
> - 'invert':'logical_invert',
> - 'iscanonical':'is_canonical',
> - 'isfinite':'is_finite',
> - 'isinfinite':'is_infinite',
> - 'isnan':'is_nan',
> - 'isnormal':'is_normal',
> - 'isqnan':'is_qnan',
> - 'issigned':'is_signed',
> - 'issnan':'is_snan',
> - 'issubnormal':'is_subnormal',
> - 'iszero':'is_zero',
> - 'maxmag':'max_mag',
> - 'minmag':'min_mag',
> - 'nextminus':'next_minus',
> - 'nextplus':'next_plus',
> - 'nexttoward':'next_toward',
> - 'or':'logical_or',
> - 'reduce':'normalize',
> - 'remaindernear':'remainder_near',
> - 'samequantum':'same_quantum',
> - 'squareroot':'sqrt',
> - 'toeng':'to_eng_string',
> - 'tointegral':'to_integral_value',
> - 'tointegralx':'to_integral_exact',
> - 'tosci':'to_sci_string',
> - 'xor':'logical_xor',
> - }
> -
> -# The following functions return True/False rather than a Decimal instance
> -
> -LOGICAL_FUNCTIONS = (
> - 'is_canonical',
> - 'is_finite',
> - 'is_infinite',
> - 'is_nan',
> - 'is_normal',
> - 'is_qnan',
> - 'is_signed',
> - 'is_snan',
> - 'is_subnormal',
> - 'is_zero',
> - 'same_quantum',
> - )
> -
> -class DecimalTest(unittest.TestCase):
> - """Class which tests the Decimal class against the test cases.
> -
> - Changed for unittest.
> - """
> +# Test extra functionality in the C version (-DEXTRA_FUNCTIONALITY).
> +EXTRA_FUNCTIONALITY = True if hasattr(C, 'DecClamped') else False
> +requires_extra_functionality = unittest.skipUnless(
> + EXTRA_FUNCTIONALITY, "test requires build with -DEXTRA_FUNCTIONALITY")
> +skip_if_extra_functionality = unittest.skipIf(
> + EXTRA_FUNCTIONALITY, "test requires regular build")
> +
> +
> +class IBMTestCases(unittest.TestCase):
> + """Class which tests the Decimal class against the IBM test cases."""
> +
> def setUp(self):
> - self.context = Context()
> + self.context = self.decimal.Context()
> + self.readcontext = self.decimal.Context()
> self.ignore_list = ['#']
> - # Basically, a # means return NaN InvalidOperation.
> - # Different from a sNaN in trim
> -
> +
> + # List of individual .decTest test ids that correspond to tests that
> + # we're skipping for one reason or another.
> + self.skipped_test_ids = set([
> + # Skip implementation-specific scaleb tests.
> + 'scbx164',
> + 'scbx165',
> +
> + # For some operations (currently exp, ln, log10, power), the decNumber
> + # reference implementation imposes additional restrictions on the context
> + # and operands. These restrictions are not part of the specification;
> + # however, the effect of these restrictions does show up in some of the
> + # testcases. We skip testcases that violate these restrictions, since
> + # Decimal behaves differently from decNumber for these testcases so these
> + # testcases would otherwise fail.
> + 'expx901',
> + 'expx902',
> + 'expx903',
> + 'expx905',
> + 'lnx901',
> + 'lnx902',
> + 'lnx903',
> + 'lnx905',
> + 'logx901',
> + 'logx902',
> + 'logx903',
> + 'logx905',
> + 'powx1183',
> + 'powx1184',
> + 'powx4001',
> + 'powx4002',
> + 'powx4003',
> + 'powx4005',
> + 'powx4008',
> + 'powx4010',
> + 'powx4012',
> + 'powx4014',
> + ])
> +
> + if self.decimal == C:
> + # status has additional Subnormal, Underflow
> + self.skipped_test_ids.add('pwsx803')
> + self.skipped_test_ids.add('pwsx805')
> + # Correct rounding (skipped for decNumber, too)
> + self.skipped_test_ids.add('powx4302')
> + self.skipped_test_ids.add('powx4303')
> + self.skipped_test_ids.add('powx4342')
> + self.skipped_test_ids.add('powx4343')
> + # http://bugs.python.org/issue7049
> + self.skipped_test_ids.add('pwmx325')
> + self.skipped_test_ids.add('pwmx326')
> +
> + # Map test directives to setter functions.
> self.ChangeDict = {'precision' : self.change_precision,
> - 'rounding' : self.change_rounding_method,
> - 'maxexponent' : self.change_max_exponent,
> - 'minexponent' : self.change_min_exponent,
> - 'clamp' : self.change_clamp}
> + 'rounding' : self.change_rounding_method,
> + 'maxexponent' : self.change_max_exponent,
> + 'minexponent' : self.change_min_exponent,
> + 'clamp' : self.change_clamp}
> +
> + # Name adapter to be able to change the Decimal and Context
> + # interface without changing the test files from Cowlishaw.
> + self.NameAdapter = {'and':'logical_and',
> + 'apply':'_apply',
> + 'class':'number_class',
> + 'comparesig':'compare_signal',
> + 'comparetotal':'compare_total',
> + 'comparetotmag':'compare_total_mag',
> + 'copy':'copy_decimal',
> + 'copyabs':'copy_abs',
> + 'copynegate':'copy_negate',
> + 'copysign':'copy_sign',
> + 'divideint':'divide_int',
> + 'invert':'logical_invert',
> + 'iscanonical':'is_canonical',
> + 'isfinite':'is_finite',
> + 'isinfinite':'is_infinite',
> + 'isnan':'is_nan',
> + 'isnormal':'is_normal',
> + 'isqnan':'is_qnan',
> + 'issigned':'is_signed',
> + 'issnan':'is_snan',
> + 'issubnormal':'is_subnormal',
> + 'iszero':'is_zero',
> + 'maxmag':'max_mag',
> + 'minmag':'min_mag',
> + 'nextminus':'next_minus',
> + 'nextplus':'next_plus',
> + 'nexttoward':'next_toward',
> + 'or':'logical_or',
> + 'reduce':'normalize',
> + 'remaindernear':'remainder_near',
> + 'samequantum':'same_quantum',
> + 'squareroot':'sqrt',
> + 'toeng':'to_eng_string',
> + 'tointegral':'to_integral_value',
> + 'tointegralx':'to_integral_exact',
> + 'tosci':'to_sci_string',
> + 'xor':'logical_xor'}
> +
> + # Map test-case names to roundings.
> + self.RoundingDict = {'ceiling' : self.decimal.ROUND_CEILING,
> + 'down' : self.decimal.ROUND_DOWN,
> + 'floor' : self.decimal.ROUND_FLOOR,
> + 'half_down' : self.decimal.ROUND_HALF_DOWN,
> + 'half_even' : self.decimal.ROUND_HALF_EVEN,
> + 'half_up' : self.decimal.ROUND_HALF_UP,
> + 'up' : self.decimal.ROUND_UP,
> + '05up' : self.decimal.ROUND_05UP}
> +
> + # Map the test cases' error names to the actual errors.
> + self.ErrorNames = {'clamped' : self.decimal.Clamped,
> + 'conversion_syntax' : self.decimal.InvalidOperation,
> + 'division_by_zero' : self.decimal.DivisionByZero,
> + 'division_impossible' : self.decimal.InvalidOperation,
> + 'division_undefined' : self.decimal.InvalidOperation,
> + 'inexact' : self.decimal.Inexact,
> + 'invalid_context' : self.decimal.InvalidOperation,
> + 'invalid_operation' : self.decimal.InvalidOperation,
> + 'overflow' : self.decimal.Overflow,
> + 'rounded' : self.decimal.Rounded,
> + 'subnormal' : self.decimal.Subnormal,
> + 'underflow' : self.decimal.Underflow}
> +
> + # The following functions return True/False rather than a
> + # Decimal instance.
> + self.LogicalFunctions = ('is_canonical',
> + 'is_finite',
> + 'is_infinite',
> + 'is_nan',
> + 'is_normal',
> + 'is_qnan',
> + 'is_signed',
> + 'is_snan',
> + 'is_subnormal',
> + 'is_zero',
> + 'same_quantum')
> +
> + def read_unlimited(self, v, context):
> + """Work around the limitations of the 32-bit _decimal version. The
> + guaranteed maximum values for prec, Emax etc. are 425000000,
> + but higher values usually work, except for rare corner cases.
> + In particular, all of the IBM tests pass with maximum values
> + of 1070000000."""
> + if self.decimal == C and self.decimal.MAX_EMAX == 425000000:
> + self.readcontext._unsafe_setprec(1070000000)
> + self.readcontext._unsafe_setemax(1070000000)
> + self.readcontext._unsafe_setemin(-1070000000)
> + return self.readcontext.create_decimal(v)
> + else:
> + return self.decimal.Decimal(v, context)
>> def eval_file(self, file):
> global skip_expected
> @@ -227,7 +290,7 @@
> #print line
> try:
> t = self.eval_line(line)
> - except DecimalException as exception:
> + except self.decimal.DecimalException as exception:
> #Exception raised where there shouldn't have been one.
> self.fail('Exception "'+exception.__class__.__name__ + '" raised on line '+line)
>> @@ -254,23 +317,23 @@
> def eval_directive(self, s):
> funct, value = (x.strip().lower() for x in s.split(':'))
> if funct == 'rounding':
> - value = RoundingDict[value]
> + value = self.RoundingDict[value]
> else:
> try:
> value = int(value)
> except ValueError:
> pass
>> - funct = self.ChangeDict.get(funct, Nonfunction)
> + funct = self.ChangeDict.get(funct, (lambda *args: None))
> funct(value)
>> def eval_equation(self, s):
> - #global DEFAULT_PRECISION
> - #print DEFAULT_PRECISION
>> if not TEST_ALL and random.random() < 0.90:
> return
>> + self.context.clear_flags()
> +
> try:
> Sides = s.split('->')
> L = Sides[0].strip().split()
> @@ -283,26 +346,26 @@
> ans = L[0]
> exceptions = L[1:]
> except (TypeError, AttributeError, IndexError):
> - raise InvalidOperation
> + raise self.decimal.InvalidOperation
> def FixQuotes(val):
> val = val.replace("''", 'SingleQuote').replace('""', 'DoubleQuote')
> val = val.replace("'", '').replace('"', '')
> val = val.replace('SingleQuote', "'").replace('DoubleQuote', '"')
> return val
>> - if id in skipped_test_ids:
> + if id in self.skipped_test_ids:
> return
>> - fname = nameAdapter.get(funct, funct)
> + fname = self.NameAdapter.get(funct, funct)
> if fname == 'rescale':
> return
> funct = getattr(self.context, fname)
> vals = []
> conglomerate = ''
> quote = 0
> - theirexceptions = [ErrorNames[x.lower()] for x in exceptions]
> -
> - for exception in Signals:
> + theirexceptions = [self.ErrorNames[x.lower()] for x in exceptions]
> +
> + for exception in Signals[self.decimal]:
> self.context.traps[exception] = 1 #Catch these bugs...
> for exception in theirexceptions:
> self.context.traps[exception] = 0
> @@ -324,7 +387,7 @@
> funct(self.context.create_decimal(v))
> except error:
> pass
> - except Signals as e:
> + except Signals[self.decimal] as e:
> self.fail("Raised %s in %s when %s disabled" % \
> (e, s, error))
> else:
> @@ -332,7 +395,7 @@
> self.context.traps[error] = 0
> v = self.context.create_decimal(v)
> else:
> - v = Decimal(v, self.context)
> + v = self.read_unlimited(v, self.context)
> vals.append(v)
>> ans = FixQuotes(ans)
> @@ -344,7 +407,7 @@
> funct(*vals)
> except error:
> pass
> - except Signals as e:
> + except Signals[self.decimal] as e:
> self.fail("Raised %s in %s when %s disabled" % \
> (e, s, error))
> else:
> @@ -352,14 +415,14 @@
> self.context.traps[error] = 0
>> # as above, but add traps cumulatively, to check precedence
> - ordered_errors = [e for e in OrderedSignals if e in theirexceptions]
> + ordered_errors = [e for e in OrderedSignals[self.decimal] if e in theirexceptions]
> for error in ordered_errors:
> self.context.traps[error] = 1
> try:
> funct(*vals)
> except error:
> pass
> - except Signals as e:
> + except Signals[self.decimal] as e:
> self.fail("Raised %s in %s; expected %s" %
> (type(e), s, error))
> else:
> @@ -373,54 +436,69 @@
> print("--", self.context)
> try:
> result = str(funct(*vals))
> - if fname in LOGICAL_FUNCTIONS:
> + if fname in self.LogicalFunctions:
> result = str(int(eval(result))) # 'True', 'False' -> '1', '0'
> - except Signals as error:
> + except Signals[self.decimal] as error:
> self.fail("Raised %s in %s" % (error, s))
> except: #Catch any error long enough to state the test case.
> print("ERROR:", s)
> raise
>> myexceptions = self.getexceptions()
> - self.context.clear_flags()
>> myexceptions.sort(key=repr)
> theirexceptions.sort(key=repr)
>> self.assertEqual(result, ans,
> 'Incorrect answer for ' + s + ' -- got ' + result)
> +
> self.assertEqual(myexceptions, theirexceptions,
> 'Incorrect flags set in ' + s + ' -- got ' + str(myexceptions))
> return
>> def getexceptions(self):
> - return [e for e in Signals if self.context.flags[e]]
> + return [e for e in Signals[self.decimal] if self.context.flags[e]]
>> def change_precision(self, prec):
> - self.context.prec = prec
> + if self.decimal == C and self.decimal.MAX_PREC == 425000000:
> + self.context._unsafe_setprec(prec)
> + else:
> + self.context.prec = prec
> def change_rounding_method(self, rounding):
> self.context.rounding = rounding
> def change_min_exponent(self, exp):
> - self.context.Emin = exp
> + if self.decimal == C and self.decimal.MAX_PREC == 425000000:
> + self.context._unsafe_setemin(exp)
> + else:
> + self.context.Emin = exp
> def change_max_exponent(self, exp):
> - self.context.Emax = exp
> + if self.decimal == C and self.decimal.MAX_PREC == 425000000:
> + self.context._unsafe_setemax(exp)
> + else:
> + self.context.Emax = exp
> def change_clamp(self, clamp):
> self.context.clamp = clamp
>> -
> +class CIBMTestCases(IBMTestCases):
> + decimal = C
> +class PyIBMTestCases(IBMTestCases):
> + decimal = P
>> # The following classes test the behaviour of Decimal according to PEP 327
>> -class DecimalExplicitConstructionTest(unittest.TestCase):
> +class ExplicitConstructionTest(unittest.TestCase):
> '''Unit tests for Explicit Construction cases of Decimal.'''
>> def test_explicit_empty(self):
> + Decimal = self.decimal.Decimal
> self.assertEqual(Decimal(), Decimal("0"))
>> def test_explicit_from_None(self):
> + Decimal = self.decimal.Decimal
> self.assertRaises(TypeError, Decimal, None)
>> def test_explicit_from_int(self):
> + Decimal = self.decimal.Decimal
>> #positive
> d = Decimal(45)
> @@ -438,7 +516,18 @@
> d = Decimal(0)
> self.assertEqual(str(d), '0')
>> + # single word longs
> + for n in range(0, 32):
> + for sign in (-1, 1):
> + for x in range(-5, 5):
> + i = sign * (2**n + x)
> + d = Decimal(i)
> + self.assertEqual(str(d), str(i))
> +
> def test_explicit_from_string(self):
> + Decimal = self.decimal.Decimal
> + InvalidOperation = self.decimal.InvalidOperation
> + localcontext = self.decimal.localcontext
>> #empty
> self.assertEqual(str(Decimal('')), 'NaN')
> @@ -458,8 +547,35 @@
> #leading and trailing whitespace permitted
> self.assertEqual(str(Decimal('1.3E4 \n')), '1.3E+4')
> self.assertEqual(str(Decimal(' -7.89')), '-7.89')
> + self.assertEqual(str(Decimal(" 3.45679 ")), '3.45679')
> +
> + # unicode whitespace
> + for lead in ["", ' ', '\u00a0', '\u205f']:
> + for trail in ["", ' ', '\u00a0', '\u205f']:
> + self.assertEqual(str(Decimal(lead + '9.311E+28' + trail)),
> + '9.311E+28')
> +
> + with localcontext() as c:
> + c.traps[InvalidOperation] = True
> + # Invalid string
> + self.assertRaises(InvalidOperation, Decimal, "xyz")
> + # Two arguments max
> + self.assertRaises(TypeError, Decimal, "1234", "x", "y")
> +
> + # space within the numeric part
> + self.assertRaises(InvalidOperation, Decimal, "1\u00a02\u00a03")
> + self.assertRaises(InvalidOperation, Decimal, "\u00a01\u00a02\u00a0")
> +
> + # unicode whitespace
> + self.assertRaises(InvalidOperation, Decimal, "\u00a0")
> + self.assertRaises(InvalidOperation, Decimal, "\u00a0\u00a0")
> +
> + # embedded NUL
> + self.assertRaises(InvalidOperation, Decimal, "12\u00003")
> +
>> def test_explicit_from_tuples(self):
> + Decimal = self.decimal.Decimal
>> #zero
> d = Decimal( (0, (0,), 0) )
> @@ -477,6 +593,10 @@
> d = Decimal( (1, (4, 3, 4, 9, 1, 3, 5, 3, 4), -25) )
> self.assertEqual(str(d), '-4.34913534E-17')
>> + #inf
> + d = Decimal( (0, (), "F") )
> + self.assertEqual(str(d), 'Infinity')
> +
> #wrong number of items
> self.assertRaises(ValueError, Decimal, (1, (4, 3, 4, 9, 1)) )
>> @@ -491,45 +611,63 @@
> self.assertRaises(ValueError, Decimal, (1, (4, 3, 4, 9, 1), '1') )
>> #bad coefficients
> + self.assertRaises(ValueError, Decimal, (1, "xyz", 2) )
> self.assertRaises(ValueError, Decimal, (1, (4, 3, 4, None, 1), 2) )
> self.assertRaises(ValueError, Decimal, (1, (4, -3, 4, 9, 1), 2) )
> self.assertRaises(ValueError, Decimal, (1, (4, 10, 4, 9, 1), 2) )
> self.assertRaises(ValueError, Decimal, (1, (4, 3, 4, 'a', 1), 2) )
>> + def test_explicit_from_list(self):
> + Decimal = self.decimal.Decimal
> +
> + d = Decimal([0, [0], 0])
> + self.assertEqual(str(d), '0')
> +
> + d = Decimal([1, [4, 3, 4, 9, 1, 3, 5, 3, 4], -25])
> + self.assertEqual(str(d), '-4.34913534E-17')
> +
> + d = Decimal([1, (4, 3, 4, 9, 1, 3, 5, 3, 4), -25])
> + self.assertEqual(str(d), '-4.34913534E-17')
> +
> + d = Decimal((1, [4, 3, 4, 9, 1, 3, 5, 3, 4], -25))
> + self.assertEqual(str(d), '-4.34913534E-17')
> +
> def test_explicit_from_bool(self):
> + Decimal = self.decimal.Decimal
> +
> self.assertIs(bool(Decimal(0)), False)
> self.assertIs(bool(Decimal(1)), True)
> self.assertEqual(Decimal(False), Decimal(0))
> self.assertEqual(Decimal(True), Decimal(1))
>> def test_explicit_from_Decimal(self):
> + Decimal = self.decimal.Decimal
>> #positive
> d = Decimal(45)
> e = Decimal(d)
> self.assertEqual(str(e), '45')
> - self.assertNotEqual(id(d), id(e))
>> #very large positive
> d = Decimal(500000123)
> e = Decimal(d)
> self.assertEqual(str(e), '500000123')
> - self.assertNotEqual(id(d), id(e))
>> #negative
> d = Decimal(-45)
> e = Decimal(d)
> self.assertEqual(str(e), '-45')
> - self.assertNotEqual(id(d), id(e))
>> #zero
> d = Decimal(0)
> e = Decimal(d)
> self.assertEqual(str(e), '0')
> - self.assertNotEqual(id(d), id(e))
>> @requires_IEEE_754
> def test_explicit_from_float(self):
> +
> + Decimal = self.decimal.Decimal
> +
> r = Decimal(0.1)
> self.assertEqual(type(r), Decimal)
> self.assertEqual(str(r),
> @@ -550,8 +688,11 @@
> self.assertEqual(x, float(Decimal(x))) # roundtrip
>> def test_explicit_context_create_decimal(self):
> -
> - nc = copy.copy(getcontext())
> + Decimal = self.decimal.Decimal
> + InvalidOperation = self.decimal.InvalidOperation
> + Rounded = self.decimal.Rounded
> +
> + nc = copy.copy(self.decimal.getcontext())
> nc.prec = 3
>> # empty
> @@ -592,7 +733,73 @@
> d = nc.create_decimal(prevdec)
> self.assertEqual(str(d), '5.00E+8')
>> + # more integers
> + nc.prec = 28
> + nc.traps[InvalidOperation] = True
> +
> + for v in [-2**63-1, -2**63, -2**31-1, -2**31, 0,
> + 2**31-1, 2**31, 2**63-1, 2**63]:
> + d = nc.create_decimal(v)
> + self.assertTrue(isinstance(d, Decimal))
> + self.assertEqual(int(d), v)
> +
> + nc.prec = 3
> + nc.traps[Rounded] = True
> + self.assertRaises(Rounded, nc.create_decimal, 1234)
> +
> + # from string
> + nc.prec = 28
> + self.assertEqual(str(nc.create_decimal('0E-017')), '0E-17')
> + self.assertEqual(str(nc.create_decimal('45')), '45')
> + self.assertEqual(str(nc.create_decimal('-Inf')), '-Infinity')
> + self.assertEqual(str(nc.create_decimal('NaN123')), 'NaN123')
> +
> + # invalid arguments
> + self.assertRaises(InvalidOperation, nc.create_decimal, "xyz")
> + self.assertRaises(ValueError, nc.create_decimal, (1, "xyz", -25))
> + self.assertRaises(TypeError, nc.create_decimal, "1234", "5678")
> +
> + # too many NaN payload digits
> + nc.prec = 3
> + self.assertRaises(InvalidOperation, nc.create_decimal, 'NaN12345')
> + self.assertRaises(InvalidOperation, nc.create_decimal,
> + Decimal('NaN12345'))
> +
> + nc.traps[InvalidOperation] = False
> + self.assertEqual(str(nc.create_decimal('NaN12345')), 'NaN')
> + self.assertTrue(nc.flags[InvalidOperation])
> +
> + nc.flags[InvalidOperation] = False
> + self.assertEqual(str(nc.create_decimal(Decimal('NaN12345'))), 'NaN')
> + self.assertTrue(nc.flags[InvalidOperation])
> +
> + def test_explicit_context_create_from_float(self):
> +
> + Decimal = self.decimal.Decimal
> +
> + nc = self.decimal.Context()
> + r = nc.create_decimal(0.1)
> + self.assertEqual(type(r), Decimal)
> + self.assertEqual(str(r), '0.1000000000000000055511151231')
> + self.assertTrue(nc.create_decimal(float('nan')).is_qnan())
> + self.assertTrue(nc.create_decimal(float('inf')).is_infinite())
> + self.assertTrue(nc.create_decimal(float('-inf')).is_infinite())
> + self.assertEqual(str(nc.create_decimal(float('nan'))),
> + str(nc.create_decimal('NaN')))
> + self.assertEqual(str(nc.create_decimal(float('inf'))),
> + str(nc.create_decimal('Infinity')))
> + self.assertEqual(str(nc.create_decimal(float('-inf'))),
> + str(nc.create_decimal('-Infinity')))
> + self.assertEqual(str(nc.create_decimal(float('-0.0'))),
> + str(nc.create_decimal('-0')))
> + nc.prec = 100
> + for i in range(200):
> + x = random.expovariate(0.01) * (random.random() * 2.0 - 1.0)
> + self.assertEqual(x, float(nc.create_decimal(x))) # roundtrip
> +
> def test_unicode_digits(self):
> + Decimal = self.decimal.Decimal
> +
> test_values = {
> '\uff11': '1',
> '\u0660.\u0660\u0663\u0667\u0662e-\u0663' : '0.0000372',
> @@ -601,29 +808,41 @@
> for input, expected in test_values.items():
> self.assertEqual(str(Decimal(input)), expected)
>> -
> -class DecimalImplicitConstructionTest(unittest.TestCase):
> +class CExplicitConstructionTest(ExplicitConstructionTest):
> + decimal = C
> +class PyExplicitConstructionTest(ExplicitConstructionTest):
> + decimal = P
> +
> +class ImplicitConstructionTest(unittest.TestCase):
> '''Unit tests for Implicit Construction cases of Decimal.'''
>> def test_implicit_from_None(self):
> - self.assertRaises(TypeError, eval, 'Decimal(5) + None', globals())
> + Decimal = self.decimal.Decimal
> + self.assertRaises(TypeError, eval, 'Decimal(5) + None', locals())
>> def test_implicit_from_int(self):
> + Decimal = self.decimal.Decimal
> +
> #normal
> self.assertEqual(str(Decimal(5) + 45), '50')
> #exceeding precision
> self.assertEqual(Decimal(5) + 123456789000, Decimal(123456789000))
>> def test_implicit_from_string(self):
> - self.assertRaises(TypeError, eval, 'Decimal(5) + "3"', globals())
> + Decimal = self.decimal.Decimal
> + self.assertRaises(TypeError, eval, 'Decimal(5) + "3"', locals())
>> def test_implicit_from_float(self):
> - self.assertRaises(TypeError, eval, 'Decimal(5) + 2.2', globals())
> + Decimal = self.decimal.Decimal
> + self.assertRaises(TypeError, eval, 'Decimal(5) + 2.2', locals())
>> def test_implicit_from_Decimal(self):
> + Decimal = self.decimal.Decimal
> self.assertEqual(Decimal(5) + Decimal(45), Decimal(50))
>> def test_rop(self):
> + Decimal = self.decimal.Decimal
> +
> # Allow other classes to be trained to interact with Decimals
> class E:
> def __divmod__(self, other):
> @@ -671,10 +890,16 @@
> self.assertEqual(eval('Decimal(10)' + sym + 'E()'),
> '10' + rop + 'str')
>> -
> -class DecimalFormatTest(unittest.TestCase):
> +class CImplicitConstructionTest(ImplicitConstructionTest):
> + decimal = C
> +class PyImplicitConstructionTest(ImplicitConstructionTest):
> + decimal = P
> +
> +class FormatTest(unittest.TestCase):
> '''Unit tests for the format function.'''
> def test_formatting(self):
> + Decimal = self.decimal.Decimal
> +
> # triples giving a format, a Decimal, and the expected result
> test_values = [
> ('e', '0E-15', '0e-15'),
> @@ -730,6 +955,7 @@
> ('g', '0E-7', '0e-7'),
> ('g', '-0E2', '-0e+2'),
> ('.0g', '3.14159265', '3'), # 0 sig fig -> 1 sig fig
> + ('.0n', '3.14159265', '3'), # same for 'n'
> ('.1g', '3.14159265', '3'),
> ('.2g', '3.14159265', '3.1'),
> ('.5g', '3.14159265', '3.1416'),
> @@ -814,56 +1040,60 @@
>> # issue 6850
> ('a=-7.0', '0.12345', 'aaaa0.1'),
> -
> - # Issue 7094: Alternate formatting (specified by #)
> - ('.0e', '1.0', '1e+0'),
> - ('#.0e', '1.0', '1.e+0'),
> - ('.0f', '1.0', '1'),
> - ('#.0f', '1.0', '1.'),
> - ('g', '1.1', '1.1'),
> - ('#g', '1.1', '1.1'),
> - ('.0g', '1', '1'),
> - ('#.0g', '1', '1.'),
> - ('.0%', '1.0', '100%'),
> - ('#.0%', '1.0', '100.%'),
> ]
> for fmt, d, result in test_values:
> self.assertEqual(format(Decimal(d), fmt), result)
>> + # bytes format argument
> + self.assertRaises(TypeError, Decimal(1).__format__, b'-020')
> +
> def test_n_format(self):
> + Decimal = self.decimal.Decimal
> +
> try:
> from locale import CHAR_MAX
> except ImportError:
> return
>> + def make_grouping(lst):
> + return ''.join([chr(x) for x in lst]) if self.decimal == C else lst
> +
> + def get_fmt(x, override=None, fmt='n'):
> + if self.decimal == C:
> + return Decimal(x).__format__(fmt, override)
> + else:
> + return Decimal(x).__format__(fmt, _localeconv=override)
> +
> # Set up some localeconv-like dictionaries
> en_US = {
> 'decimal_point' : '.',
> - 'grouping' : [3, 3, 0],
> - 'thousands_sep': ','
> + 'grouping' : make_grouping([3, 3, 0]),
> + 'thousands_sep' : ','
> }
>> fr_FR = {
> 'decimal_point' : ',',
> - 'grouping' : [CHAR_MAX],
> + 'grouping' : make_grouping([CHAR_MAX]),
> 'thousands_sep' : ''
> }
>> ru_RU = {
> 'decimal_point' : ',',
> - 'grouping' : [3, 3, 0],
> + 'grouping': make_grouping([3, 3, 0]),
> 'thousands_sep' : ' '
> }
>> crazy = {
> 'decimal_point' : '&',
> - 'grouping' : [1, 4, 2, CHAR_MAX],
> + 'grouping': make_grouping([1, 4, 2, CHAR_MAX]),
> 'thousands_sep' : '-'
> }
>> -
> - def get_fmt(x, locale, fmt='n'):
> - return Decimal.__format__(Decimal(x), fmt, _localeconv=locale)
> + dotsep_wide = {
> + 'decimal_point' : b'\xc2\xbf'.decode('utf-8'),
> + 'grouping': make_grouping([3, 3, 0]),
> + 'thousands_sep' : b'\xc2\xb4'.decode('utf-8')
> + }
>> self.assertEqual(get_fmt(Decimal('12.7'), en_US), '12.7')
> self.assertEqual(get_fmt(Decimal('12.7'), fr_FR), '12,7')
> @@ -902,11 +1132,33 @@
> self.assertEqual(get_fmt(123456, crazy, '012n'), '00-01-2345-6')
> self.assertEqual(get_fmt(123456, crazy, '013n'), '000-01-2345-6')
>> -
> -class DecimalArithmeticOperatorsTest(unittest.TestCase):
> + # wide char separator and decimal point
> + self.assertEqual(get_fmt(Decimal('-1.5'), dotsep_wide, '020n'),
> + '-0\u00b4000\u00b4000\u00b4000\u00b4001\u00bf5')
> +
> + def test_wide_char_separator_decimal_point(self):
> + # locale with wide char separator and decimal point
> + Decimal = self.decimal.Decimal
> +
> + try:
> + locale.setlocale(locale.LC_ALL, 'ps_AF')
> + except locale.Error:
> + return
> +
> + self.assertEqual(format(Decimal('100000000.123'), 'n'),
> + '100\u066c000\u066c000\u066b123')
> + locale.resetlocale()
> +
> +class CFormatTest(FormatTest):
> + decimal = C
> +class PyFormatTest(FormatTest):
> + decimal = P
> +
> +class ArithmeticOperatorsTest(unittest.TestCase):
> '''Unit tests for all arithmetic operators, binary and unary.'''
>> def test_addition(self):
> + Decimal = self.decimal.Decimal
>> d1 = Decimal('-11.1')
> d2 = Decimal('22.2')
> @@ -934,6 +1186,7 @@
> self.assertEqual(d1, Decimal('16.1'))
>> def test_subtraction(self):
> + Decimal = self.decimal.Decimal
>> d1 = Decimal('-11.1')
> d2 = Decimal('22.2')
> @@ -961,6 +1214,7 @@
> self.assertEqual(d1, Decimal('-38.3'))
>> def test_multiplication(self):
> + Decimal = self.decimal.Decimal
>> d1 = Decimal('-5')
> d2 = Decimal('3')
> @@ -988,6 +1242,7 @@
> self.assertEqual(d1, Decimal('-75'))
>> def test_division(self):
> + Decimal = self.decimal.Decimal
>> d1 = Decimal('-5')
> d2 = Decimal('2')
> @@ -1015,6 +1270,7 @@
> self.assertEqual(d1, Decimal('-0.625'))
>> def test_floor_division(self):
> + Decimal = self.decimal.Decimal
>> d1 = Decimal('5')
> d2 = Decimal('2')
> @@ -1042,6 +1298,7 @@
> self.assertEqual(d1, Decimal('1'))
>> def test_powering(self):
> + Decimal = self.decimal.Decimal
>> d1 = Decimal('5')
> d2 = Decimal('2')
> @@ -1069,6 +1326,7 @@
> self.assertEqual(d1, Decimal('390625'))
>> def test_module(self):
> + Decimal = self.decimal.Decimal
>> d1 = Decimal('5')
> d2 = Decimal('2')
> @@ -1096,6 +1354,7 @@
> self.assertEqual(d1, Decimal('1'))
>> def test_floor_div_module(self):
> + Decimal = self.decimal.Decimal
>> d1 = Decimal('5')
> d2 = Decimal('2')
> @@ -1122,6 +1381,8 @@
> self.assertEqual(type(q), type(d1))
>> def test_unary_operators(self):
> + Decimal = self.decimal.Decimal
> +
> self.assertEqual(+Decimal(45), Decimal(+45)) # +
> self.assertEqual(-Decimal(45), Decimal(-45)) # -
> self.assertEqual(abs(Decimal(45)), abs(Decimal(-45))) # abs
> @@ -1134,6 +1395,9 @@
>> # equality comparisons (==, !=) involving only quiet nans
> # don't signal, but return False or True respectively.
> + Decimal = self.decimal.Decimal
> + InvalidOperation = self.decimal.InvalidOperation
> + localcontext = self.decimal.localcontext
>> n = Decimal('NaN')
> s = Decimal('sNaN')
> @@ -1179,53 +1443,124 @@
> self.assertRaises(InvalidOperation, op, x, y)
>> def test_copy_sign(self):
> + Decimal = self.decimal.Decimal
> +
> d = Decimal(1).copy_sign(Decimal(-2))
> -
> self.assertEqual(Decimal(1).copy_sign(-2), d)
> self.assertRaises(TypeError, Decimal(1).copy_sign, '-2')
>> +class CArithmeticOperatorsTest(ArithmeticOperatorsTest):
> + decimal = C
> +class PyArithmeticOperatorsTest(ArithmeticOperatorsTest):
> + decimal = P
> +
> # The following are two functions used to test threading in the next class
>> def thfunc1(cls):
> + Decimal = cls.decimal.Decimal
> + InvalidOperation = cls.decimal.InvalidOperation
> + DivisionByZero = cls.decimal.DivisionByZero
> + Overflow = cls.decimal.Overflow
> + Underflow = cls.decimal.Underflow
> + Inexact = cls.decimal.Inexact
> + getcontext = cls.decimal.getcontext
> + localcontext = cls.decimal.localcontext
> +
> d1 = Decimal(1)
> d3 = Decimal(3)
> test1 = d1/d3
> +
> + cls.finish1.set()
> cls.synchro.wait()
> +
> test2 = d1/d3
> - cls.finish1.set()
> -
> - cls.assertEqual(test1, Decimal('0.3333333333333333333333333333'))
> - cls.assertEqual(test2, Decimal('0.3333333333333333333333333333'))
> + with localcontext() as c2:
> + cls.assertTrue(c2.flags[Inexact])
> + cls.assertRaises(DivisionByZero, c2.divide, d1, 0)
> + cls.assertTrue(c2.flags[DivisionByZero])
> + with localcontext() as c3:
> + cls.assertTrue(c3.flags[Inexact])
> + cls.assertTrue(c3.flags[DivisionByZero])
> + cls.assertRaises(InvalidOperation, c3.compare, d1, Decimal('sNaN'))
> + cls.assertTrue(c3.flags[InvalidOperation])
> + del c3
> + cls.assertFalse(c2.flags[InvalidOperation])
> + del c2
> +
> + cls.assertEqual(test1, Decimal('0.333333333333333333333333'))
> + cls.assertEqual(test2, Decimal('0.333333333333333333333333'))
> +
> + c1 = getcontext()
> + cls.assertTrue(c1.flags[Inexact])
> + for sig in Overflow, Underflow, DivisionByZero, InvalidOperation:
> + cls.assertFalse(c1.flags[sig])
> return
>> def thfunc2(cls):
> + Decimal = cls.decimal.Decimal
> + InvalidOperation = cls.decimal.InvalidOperation
> + DivisionByZero = cls.decimal.DivisionByZero
> + Overflow = cls.decimal.Overflow
> + Underflow = cls.decimal.Underflow
> + Inexact = cls.decimal.Inexact
> + getcontext = cls.decimal.getcontext
> + localcontext = cls.decimal.localcontext
> +
> d1 = Decimal(1)
> d3 = Decimal(3)
> test1 = d1/d3
> +
> thiscontext = getcontext()
> thiscontext.prec = 18
> test2 = d1/d3
> +
> + with localcontext() as c2:
> + cls.assertTrue(c2.flags[Inexact])
> + cls.assertRaises(Overflow, c2.multiply, Decimal('1e425000000'), 999)
> + cls.assertTrue(c2.flags[Overflow])
> + with localcontext(thiscontext) as c3:
> + cls.assertTrue(c3.flags[Inexact])
> + cls.assertFalse(c3.flags[Overflow])
> + c3.traps[Underflow] = True
> + cls.assertRaises(Underflow, c3.divide, Decimal('1e-425000000'), 999)
> + cls.assertTrue(c3.flags[Underflow])
> + del c3
> + cls.assertFalse(c2.flags[Underflow])
> + cls.assertFalse(c2.traps[Underflow])
> + del c2
> +
> cls.synchro.set()
> cls.finish2.set()
>> - cls.assertEqual(test1, Decimal('0.3333333333333333333333333333'))
> + cls.assertEqual(test1, Decimal('0.333333333333333333333333'))
> cls.assertEqual(test2, Decimal('0.333333333333333333'))
> +
> + cls.assertFalse(thiscontext.traps[Underflow])
> + cls.assertTrue(thiscontext.flags[Inexact])
> + for sig in Overflow, Underflow, DivisionByZero, InvalidOperation:
> + cls.assertFalse(thiscontext.flags[sig])
> return
>> -
> -class DecimalUseOfContextTest(unittest.TestCase):
> - '''Unit tests for Use of Context cases in Decimal.'''
> -
> - try:
> - import threading
> - except ImportError:
> - threading = None
> +class ThreadingTest(unittest.TestCase):
> + '''Unit tests for thread local contexts in Decimal.'''
>> # Take care executing this test from IDLE, there's an issue in threading
> # that hangs IDLE and I couldn't find it
>> def test_threading(self):
> - #Test the "threading isolation" of a Context.
> + DefaultContext = self.decimal.DefaultContext
> +
> + if self.decimal == C and not self.decimal.HAVE_THREADS:
> + self.skipTest("compiled without threading")
> + # Test the "threading isolation" of a Context. Also test changing
> + # the DefaultContext, which acts as a template for the thread-local
> + # contexts.
> + save_prec = DefaultContext.prec
> + save_emax = DefaultContext.Emax
> + save_emin = DefaultContext.Emin
> + DefaultContext.prec = 24
> + DefaultContext.Emax = 425000000
> + DefaultContext.Emin = -425000000
>> self.synchro = threading.Event()
> self.finish1 = threading.Event()
> @@ -1239,17 +1574,29 @@
>> self.finish1.wait()
> self.finish2.wait()
> +
> + for sig in Signals[self.decimal]:
> + self.assertFalse(DefaultContext.flags[sig])
> +
> + DefaultContext.prec = save_prec
> + DefaultContext.Emax = save_emax
> + DefaultContext.Emin = save_emin
> return
>> - if threading is None:
> - del test_threading
> -
> -
> -class DecimalUsabilityTest(unittest.TestCase):
> + at unittest.skipUnless(threading, 'threading required')
> +class CThreadingTest(ThreadingTest):
> + decimal = C
> + at unittest.skipUnless(threading, 'threading required')
> +class PyThreadingTest(ThreadingTest):
> + decimal = P
> +
> +class UsabilityTest(unittest.TestCase):
> '''Unit tests for Usability cases of Decimal.'''
>> def test_comparison_operators(self):
>> + Decimal = self.decimal.Decimal
> +
> da = Decimal('23.42')
> db = Decimal('23.42')
> dc = Decimal('45')
> @@ -1283,6 +1630,8 @@
> self.assertEqual(a, b)
>> def test_decimal_float_comparison(self):
> + Decimal = self.decimal.Decimal
> +
> da = Decimal('0.25')
> db = Decimal('3.0')
> self.assertLess(da, 3.0)
> @@ -1299,7 +1648,71 @@
> self.assertEqual(3.0, db)
> self.assertNotEqual(0.1, Decimal('0.1'))
>> + def test_decimal_complex_comparison(self):
> + Decimal = self.decimal.Decimal
> +
> + da = Decimal('0.25')
> + db = Decimal('3.0')
> + self.assertNotEqual(da, (1.5+0j))
> + self.assertNotEqual((1.5+0j), da)
> + self.assertEqual(da, (0.25+0j))
> + self.assertEqual((0.25+0j), da)
> + self.assertEqual((3.0+0j), db)
> + self.assertEqual(db, (3.0+0j))
> +
> + self.assertNotEqual(db, (3.0+1j))
> + self.assertNotEqual((3.0+1j), db)
> +
> + self.assertIs(db.__lt__(3.0+0j), NotImplemented)
> + self.assertIs(db.__le__(3.0+0j), NotImplemented)
> + self.assertIs(db.__gt__(3.0+0j), NotImplemented)
> + self.assertIs(db.__le__(3.0+0j), NotImplemented)
> +
> + def test_decimal_fraction_comparison(self):
> + D = self.decimal.Decimal
> + F = fractions[self.decimal].Fraction
> + Context = self.decimal.Context
> + localcontext = self.decimal.localcontext
> + InvalidOperation = self.decimal.InvalidOperation
> +
> +
> + emax = C.MAX_EMAX if C else 999999999
> + emin = C.MIN_EMIN if C else -999999999
> + etiny = C.MIN_ETINY if C else -1999999997
> + c = Context(Emax=emax, Emin=emin)
> +
> + with localcontext(c):
> + c.prec = emax
> + self.assertLess(D(0), F(1,9999999999999999999999999999999999999))
> + self.assertLess(F(-1,9999999999999999999999999999999999999), D(0))
> + self.assertLess(F(0,1), D("1e" + str(etiny)))
> + self.assertLess(D("-1e" + str(etiny)), F(0,1))
> + self.assertLess(F(0,9999999999999999999999999), D("1e" + str(etiny)))
> + self.assertLess(D("-1e" + str(etiny)), F(0,9999999999999999999999999))
> +
> + self.assertEqual(D("0.1"), F(1,10))
> + self.assertEqual(F(1,10), D("0.1"))
> +
> + c.prec = 300
> + self.assertNotEqual(D(1)/3, F(1,3))
> + self.assertNotEqual(F(1,3), D(1)/3)
> +
> + self.assertLessEqual(F(120984237, 9999999999), D("9e" + str(emax)))
> + self.assertGreaterEqual(D("9e" + str(emax)), F(120984237, 9999999999))
> +
> + self.assertGreater(D('inf'), F(99999999999,123))
> + self.assertGreater(D('inf'), F(-99999999999,123))
> + self.assertLess(D('-inf'), F(99999999999,123))
> + self.assertLess(D('-inf'), F(-99999999999,123))
> +
> + self.assertRaises(InvalidOperation, D('nan').__gt__, F(-9,123))
> + self.assertIs(NotImplemented, F(-9,123).__lt__(D('nan')))
> + self.assertNotEqual(D('nan'), F(-9,123))
> + self.assertNotEqual(F(-9,123), D('nan'))
> +
> def test_copy_and_deepcopy_methods(self):
> + Decimal = self.decimal.Decimal
> +
> d = Decimal('43.24')
> c = copy.copy(d)
> self.assertEqual(id(c), id(d))
> @@ -1307,6 +1720,10 @@
> self.assertEqual(id(dc), id(d))
>> def test_hash_method(self):
> +
> + Decimal = self.decimal.Decimal
> + localcontext = self.decimal.localcontext
> +
> def hashit(d):
> a = hash(d)
> b = d.__hash__()
> @@ -1367,24 +1784,27 @@
> d = Decimal(s)
> self.assertEqual(hashit(f), hashit(d))
>> - # check that the value of the hash doesn't depend on the
> - # current context (issue #1757)
> - c = getcontext()
> - old_precision = c.prec
> - x = Decimal("123456789.1")
> -
> - c.prec = 6
> - h1 = hashit(x)
> - c.prec = 10
> - h2 = hashit(x)
> - c.prec = 16
> - h3 = hashit(x)
> -
> - self.assertEqual(h1, h2)
> - self.assertEqual(h1, h3)
> - c.prec = old_precision
> + with localcontext() as c:
> + # check that the value of the hash doesn't depend on the
> + # current context (issue #1757)
> + x = Decimal("123456789.1")
> +
> + c.prec = 6
> + h1 = hashit(x)
> + c.prec = 10
> + h2 = hashit(x)
> + c.prec = 16
> + h3 = hashit(x)
> +
> + self.assertEqual(h1, h2)
> + self.assertEqual(h1, h3)
> +
> + c.prec = 10000
> + x = 1100 ** 1248
> + self.assertEqual(hashit(Decimal(x)), hashit(x))
>> def test_min_and_max_methods(self):
> + Decimal = self.decimal.Decimal
>> d1 = Decimal('15.32')
> d2 = Decimal('28.5')
> @@ -1404,6 +1824,8 @@
> self.assertIs(max(d2,l1), d2)
>> def test_as_nonzero(self):
> + Decimal = self.decimal.Decimal
> +
> #as false
> self.assertFalse(Decimal(0))
> #as true
> @@ -1411,6 +1833,7 @@
>> def test_tostring_methods(self):
> #Test str and repr methods.
> + Decimal = self.decimal.Decimal
>> d = Decimal('15.32')
> self.assertEqual(str(d), '15.32') # str
> @@ -1418,6 +1841,7 @@
>> def test_tonum_methods(self):
> #Test float and int methods.
> + Decimal = self.decimal.Decimal
>> d1 = Decimal('66')
> d2 = Decimal('15.32')
> @@ -1440,6 +1864,7 @@
> ('-11.0', -11),
> ('0.0', 0),
> ('-0E3', 0),
> + ('89891211712379812736.1', 89891211712379812736),
> ]
> for d, i in test_pairs:
> self.assertEqual(math.floor(Decimal(d)), i)
> @@ -1459,6 +1884,7 @@
> ('-11.0', -11),
> ('0.0', 0),
> ('-0E3', 0),
> + ('89891211712379812736.1', 89891211712379812737),
> ]
> for d, i in test_pairs:
> self.assertEqual(math.ceil(Decimal(d)), i)
> @@ -1516,9 +1942,8 @@
> for d, n, r in test_triples:
> self.assertEqual(str(round(Decimal(d), n)), r)
>> -
> -
> def test_eval_round_trip(self):
> + Decimal = self.decimal.Decimal
>> #with zero
> d = Decimal( (0, (0,), 0) )
> @@ -1537,6 +1962,7 @@
> self.assertEqual(d, eval(repr(d)))
>> def test_as_tuple(self):
> + Decimal = self.decimal.Decimal
>> #with zero
> d = Decimal(0)
> @@ -1550,7 +1976,7 @@
> d = Decimal("-4.34913534E-17")
> self.assertEqual(d.as_tuple(), (1, (4, 3, 4, 9, 1, 3, 5, 3, 4), -25) )
>> - #inf
> + # XXX non-compliant infinity payload.
> d = Decimal("Infinity")
> self.assertEqual(d.as_tuple(), (0, (0,), 'F') )
>> @@ -1570,14 +1996,2158 @@
> d = Decimal( (1, (), 'n') )
> self.assertEqual(d.as_tuple(), (1, (), 'n') )
>> - #coefficient in infinity should be ignored
> - d = Decimal( (0, (4, 5, 3, 4), 'F') )
> - self.assertEqual(d.as_tuple(), (0, (0,), 'F'))
> - d = Decimal( (1, (0, 2, 7, 1), 'F') )
> - self.assertEqual(d.as_tuple(), (1, (0,), 'F'))
> -
> - def test_immutability_operations(self):
> + # XXX coefficient in infinity should raise an error
> + if self.decimal == P:
> + d = Decimal( (0, (4, 5, 3, 4), 'F') )
> + self.assertEqual(d.as_tuple(), (0, (0,), 'F'))
> + d = Decimal( (1, (0, 2, 7, 1), 'F') )
> + self.assertEqual(d.as_tuple(), (1, (0,), 'F'))
> +
> + def test_subclassing(self):
> + # Different behaviours when subclassing Decimal
> + Decimal = self.decimal.Decimal
> +
> + class MyDecimal(Decimal):
> + pass
> +
> + d1 = MyDecimal(1)
> + d2 = MyDecimal(2)
> + d = d1 + d2
> + self.assertIs(type(d), Decimal)
> +
> + d = d1.max(d2)
> + self.assertIs(type(d), Decimal)
> +
> + d = copy.copy(d1)
> + self.assertIs(type(d), MyDecimal)
> + self.assertEqual(d, d1)
> +
> + d = copy.deepcopy(d1)
> + self.assertIs(type(d), MyDecimal)
> + self.assertEqual(d, d1)
> +
> + def test_implicit_context(self):
> + Decimal = self.decimal.Decimal
> + getcontext = self.decimal.getcontext
> +
> + # Check results when context given implicitly. (Issue 2478)
> + c = getcontext()
> + self.assertEqual(str(Decimal(0).sqrt()),
> + str(c.sqrt(Decimal(0))))
> +
> + def test_conversions_from_int(self):
> + # Check that methods taking a second Decimal argument will
> + # always accept an integer in place of a Decimal.
> + Decimal = self.decimal.Decimal
> +
> + self.assertEqual(Decimal(4).compare(3),
> + Decimal(4).compare(Decimal(3)))
> + self.assertEqual(Decimal(4).compare_signal(3),
> + Decimal(4).compare_signal(Decimal(3)))
> + self.assertEqual(Decimal(4).compare_total(3),
> + Decimal(4).compare_total(Decimal(3)))
> + self.assertEqual(Decimal(4).compare_total_mag(3),
> + Decimal(4).compare_total_mag(Decimal(3)))
> + self.assertEqual(Decimal(10101).logical_and(1001),
> + Decimal(10101).logical_and(Decimal(1001)))
> + self.assertEqual(Decimal(10101).logical_or(1001),
> + Decimal(10101).logical_or(Decimal(1001)))
> + self.assertEqual(Decimal(10101).logical_xor(1001),
> + Decimal(10101).logical_xor(Decimal(1001)))
> + self.assertEqual(Decimal(567).max(123),
> + Decimal(567).max(Decimal(123)))
> + self.assertEqual(Decimal(567).max_mag(123),
> + Decimal(567).max_mag(Decimal(123)))
> + self.assertEqual(Decimal(567).min(123),
> + Decimal(567).min(Decimal(123)))
> + self.assertEqual(Decimal(567).min_mag(123),
> + Decimal(567).min_mag(Decimal(123)))
> + self.assertEqual(Decimal(567).next_toward(123),
> + Decimal(567).next_toward(Decimal(123)))
> + self.assertEqual(Decimal(1234).quantize(100),
> + Decimal(1234).quantize(Decimal(100)))
> + self.assertEqual(Decimal(768).remainder_near(1234),
> + Decimal(768).remainder_near(Decimal(1234)))
> + self.assertEqual(Decimal(123).rotate(1),
> + Decimal(123).rotate(Decimal(1)))
> + self.assertEqual(Decimal(1234).same_quantum(1000),
> + Decimal(1234).same_quantum(Decimal(1000)))
> + self.assertEqual(Decimal('9.123').scaleb(-100),
> + Decimal('9.123').scaleb(Decimal(-100)))
> + self.assertEqual(Decimal(456).shift(-1),
> + Decimal(456).shift(Decimal(-1)))
> +
> + self.assertEqual(Decimal(-12).fma(Decimal(45), 67),
> + Decimal(-12).fma(Decimal(45), Decimal(67)))
> + self.assertEqual(Decimal(-12).fma(45, 67),
> + Decimal(-12).fma(Decimal(45), Decimal(67)))
> + self.assertEqual(Decimal(-12).fma(45, Decimal(67)),
> + Decimal(-12).fma(Decimal(45), Decimal(67)))
> +
> +class CUsabilityTest(UsabilityTest):
> + decimal = C
> +class PyUsabilityTest(UsabilityTest):
> + decimal = P
> +
> +class PythonAPItests(unittest.TestCase):
> +
> + def test_abc(self):
> + Decimal = self.decimal.Decimal
> +
> + self.assertTrue(issubclass(Decimal, numbers.Number))
> + self.assertFalse(issubclass(Decimal, numbers.Real))
> + self.assertIsInstance(Decimal(0), numbers.Number)
> + self.assertNotIsInstance(Decimal(0), numbers.Real)
> +
> + def test_pickle(self):
> + Decimal = self.decimal.Decimal
> +
> + savedecimal = sys.modules['decimal']
> +
> + # Round trip
> + sys.modules['decimal'] = self.decimal
> + d = Decimal('-3.141590000')
> + p = pickle.dumps(d)
> + e = pickle.loads(p)
> + self.assertEqual(d, e)
> +
> + if C:
> + # Test interchangeability
> + x = C.Decimal('-3.123e81723')
> + y = P.Decimal('-3.123e81723')
> +
> + sys.modules['decimal'] = C
> + sx = pickle.dumps(x)
> + sys.modules['decimal'] = P
> + r = pickle.loads(sx)
> + self.assertIsInstance(r, P.Decimal)
> + self.assertEqual(r, y)
> +
> + sys.modules['decimal'] = P
> + sy = pickle.dumps(y)
> + sys.modules['decimal'] = C
> + r = pickle.loads(sy)
> + self.assertIsInstance(r, C.Decimal)
> + self.assertEqual(r, x)
> +
> + sys.modules['decimal'] = savedecimal
> +
> + def test_int(self):
> + Decimal = self.decimal.Decimal
> + ROUND_DOWN = self.decimal.ROUND_DOWN
> +
> + for x in range(-250, 250):
> + s = '%0.2f' % (x / 100.0)
> + # should work the same as for floats
> + self.assertEqual(int(Decimal(s)), int(float(s)))
> + # should work the same as to_integral in the ROUND_DOWN mode
> + d = Decimal(s)
> + r = d.to_integral(ROUND_DOWN)
> + self.assertEqual(Decimal(int(d)), r)
> +
> + self.assertRaises(ValueError, int, Decimal('-nan'))
> + self.assertRaises(ValueError, int, Decimal('snan'))
> + self.assertRaises(OverflowError, int, Decimal('inf'))
> + self.assertRaises(OverflowError, int, Decimal('-inf'))
> +
> + def test_trunc(self):
> + Decimal = self.decimal.Decimal
> + ROUND_DOWN = self.decimal.ROUND_DOWN
> +
> + for x in range(-250, 250):
> + s = '%0.2f' % (x / 100.0)
> + # should work the same as for floats
> + self.assertEqual(int(Decimal(s)), int(float(s)))
> + # should work the same as to_integral in the ROUND_DOWN mode
> + d = Decimal(s)
> + r = d.to_integral(ROUND_DOWN)
> + self.assertEqual(Decimal(math.trunc(d)), r)
> +
> + def test_from_float(self):
> +
> + Decimal = self.decimal.Decimal
> +
> + class MyDecimal(Decimal):
> + pass
> +
> + self.assertTrue(issubclass(MyDecimal, Decimal))
> +
> + r = MyDecimal.from_float(0.1)
> + self.assertEqual(type(r), MyDecimal)
> + self.assertEqual(str(r),
> + '0.1000000000000000055511151231257827021181583404541015625')
> + bigint = 12345678901234567890123456789
> + self.assertEqual(MyDecimal.from_float(bigint), MyDecimal(bigint))
> + self.assertTrue(MyDecimal.from_float(float('nan')).is_qnan())
> + self.assertTrue(MyDecimal.from_float(float('inf')).is_infinite())
> + self.assertTrue(MyDecimal.from_float(float('-inf')).is_infinite())
> + self.assertEqual(str(MyDecimal.from_float(float('nan'))),
> + str(Decimal('NaN')))
> + self.assertEqual(str(MyDecimal.from_float(float('inf'))),
> + str(Decimal('Infinity')))
> + self.assertEqual(str(MyDecimal.from_float(float('-inf'))),
> + str(Decimal('-Infinity')))
> + self.assertRaises(TypeError, MyDecimal.from_float, 'abc')
> + for i in range(200):
> + x = random.expovariate(0.01) * (random.random() * 2.0 - 1.0)
> + self.assertEqual(x, float(MyDecimal.from_float(x))) # roundtrip
> +
> + def test_create_decimal_from_float(self):
> + Decimal = self.decimal.Decimal
> + Context = self.decimal.Context
> + ROUND_DOWN = self.decimal.ROUND_DOWN
> + ROUND_UP = self.decimal.ROUND_UP
> + Inexact = self.decimal.Inexact
> +
> + context = Context(prec=5, rounding=ROUND_DOWN)
> + self.assertEqual(
> + context.create_decimal_from_float(math.pi),
> + Decimal('3.1415')
> + )
> + context = Context(prec=5, rounding=ROUND_UP)
> + self.assertEqual(
> + context.create_decimal_from_float(math.pi),
> + Decimal('3.1416')
> + )
> + context = Context(prec=5, traps=[Inexact])
> + self.assertRaises(
> + Inexact,
> + context.create_decimal_from_float,
> + math.pi
> + )
> + self.assertEqual(repr(context.create_decimal_from_float(-0.0)),
> + "Decimal('-0')")
> + self.assertEqual(repr(context.create_decimal_from_float(1.0)),
> + "Decimal('1')")
> + self.assertEqual(repr(context.create_decimal_from_float(10)),
> + "Decimal('10')")
> +
> + def test_quantize(self):
> + Decimal = self.decimal.Decimal
> + Context = self.decimal.Context
> + InvalidOperation = self.decimal.InvalidOperation
> + ROUND_DOWN = self.decimal.ROUND_DOWN
> +
> + c = Context(Emax=99999, Emin=-99999)
> + self.assertEqual(
> + Decimal('7.335').quantize(Decimal('.01')),
> + Decimal('7.34')
> + )
> + self.assertEqual(
> + Decimal('7.335').quantize(Decimal('.01'), rounding=ROUND_DOWN),
> + Decimal('7.33')
> + )
> + self.assertRaises(
> + InvalidOperation,
> + Decimal("10e99999").quantize, Decimal('1e100000'), context=c
> + )
> +
> + c = Context()
> + d = Decimal("0.871831e800")
> + x = d.quantize(context=c, exp=Decimal("1e797"), rounding=ROUND_DOWN)
> + self.assertEqual(x, Decimal('8.71E+799'))
> +
> + def test_complex(self):
> + Decimal = self.decimal.Decimal
> +
> + x = Decimal("9.8182731e181273")
> + self.assertEqual(x.real, x)
> + self.assertEqual(x.imag, 0)
> + self.assertEqual(x.conjugate(), x)
> +
> + x = Decimal("1")
> + self.assertEqual(complex(x), complex(float(1)))
> +
> + self.assertRaises(AttributeError, setattr, x, 'real', 100)
> + self.assertRaises(AttributeError, setattr, x, 'imag', 100)
> + self.assertRaises(AttributeError, setattr, x, 'conjugate', 100)
> + self.assertRaises(AttributeError, setattr, x, '__complex__', 100)
> +
> + def test_named_parameters(self):
> + D = self.decimal.Decimal
> + Context = self.decimal.Context
> + localcontext = self.decimal.localcontext
> + ...
>> [Message clipped]
> _______________________________________________
> Python-checkins mailing list
> Python-checkins at python.org
> http://mail.python.org/mailman/listinfo/python-checkins
>
--
Thanks,
Andrew Svetlov
More information about the Python-checkins
mailing list