mirror of
https://github.com/python/cpython.git
synced 2025-10-10 00:43:41 +00:00
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.
This commit is contained in:
parent
8bfccd852e
commit
1919b7e72b
71 changed files with 31412 additions and 1031 deletions
194
Lib/decimal.py
194
Lib/decimal.py
|
@ -46,8 +46,8 @@ Decimal('1')
|
|||
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 @@ __all__ = [
|
|||
# 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 @@ __version__ = '1.70' # Highest version of the spec this complies with
|
|||
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_UP = 'ROUND_UP'
|
|||
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 @@ class Underflow(Inexact, Rounded, Subnormal):
|
|||
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 @@ _condition_map = {ConversionSyntax:InvalidOperation,
|
|||
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 @@ try:
|
|||
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 @@ class Decimal(object):
|
|||
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 @@ class Decimal(object):
|
|||
"""
|
||||
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 @@ class Decimal(object):
|
|||
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 @@ class Context(object):
|
|||
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 @@ class Context(object):
|
|||
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 @@ class Context(object):
|
|||
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 @@ class Context(object):
|
|||
>>> 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 @@ class Context(object):
|
|||
>>> 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 @@ class Context(object):
|
|||
+Normal
|
||||
+Infinity
|
||||
|
||||
>>> c = Context(ExtendedContext)
|
||||
>>> c = ExtendedContext.copy()
|
||||
>>> c.Emin = -999
|
||||
>>> c.Emax = 999
|
||||
>>> c.number_class(Decimal('Infinity'))
|
||||
|
@ -5916,6 +6032,12 @@ def _convert_for_comparison(self, other, equality_op=False):
|
|||
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 @@ DefaultContext = Context(
|
|||
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 @@ def _parse_format_specifier(format_spec, _localeconv=None):
|
|||
# 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 @@ _SignedInfinity = (_Infinity, _NegativeInfinity)
|
|||
|
||||
# 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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue