diff options
author | Stefan Krah <skrah@bytereef.org> | 2012-03-21 17:25:23 (GMT) |
---|---|---|
committer | Stefan Krah <skrah@bytereef.org> | 2012-03-21 17:25:23 (GMT) |
commit | 1919b7e72bc43315b32f38a6f5f01e8c717907f4 (patch) | |
tree | ef7490b66425fd2c622740ef9adfb03d806b7517 /Lib/decimal.py | |
parent | 8bfccd852e2bceb04664b4832fc80eb3b7584918 (diff) | |
download | cpython-1919b7e72bc43315b32f38a6f5f01e8c717907f4.zip cpython-1919b7e72bc43315b32f38a6f5f01e8c717907f4.tar.gz cpython-1919b7e72bc43315b32f38a6f5f01e8c717907f4.tar.bz2 |
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.
Diffstat (limited to 'Lib/decimal.py')
-rw-r--r-- | Lib/decimal.py | 194 |
1 files changed, 163 insertions, 31 deletions
diff --git a/Lib/decimal.py b/Lib/decimal.py index e946182..b6f66ab 100644 --- a/Lib/decimal.py +++ b/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 |