summaryrefslogtreecommitdiffstats
path: root/Lib/decimal.py
diff options
context:
space:
mode:
authorRaymond Hettinger <python@rcn.com>2004-09-19 01:54:09 (GMT)
committerRaymond Hettinger <python@rcn.com>2004-09-19 01:54:09 (GMT)
commit636a6b100fe6083388bc5315758326078abe65b4 (patch)
tree3c862815b3865e1b4b18729cbe8c4e84e7a0e8fe /Lib/decimal.py
parent6cc1350807a64f609f6dd9559f9c94a2d208cd5f (diff)
downloadcpython-636a6b100fe6083388bc5315758326078abe65b4.zip
cpython-636a6b100fe6083388bc5315758326078abe65b4.tar.gz
cpython-636a6b100fe6083388bc5315758326078abe65b4.tar.bz2
SF patch #1020845: Decimal performance enhancements
(Contributed by Nick Coghlan.) Various code cleanups and optimizations (saves about 40% on testsuite execution time and on the telco benchmark). * caches results of various operations on self (esp. checks for being a special value). * _WorkRep now uses ints and longs for intermediate computations.
Diffstat (limited to 'Lib/decimal.py')
-rw-r--r--Lib/decimal.py937
1 files changed, 437 insertions, 500 deletions
diff --git a/Lib/decimal.py b/Lib/decimal.py
index 98a8342..950af60 100644
--- a/Lib/decimal.py
+++ b/Lib/decimal.py
@@ -454,9 +454,13 @@ else:
class Decimal(object):
"""Floating point class for decimal arithmetic."""
- __slots__ = ('_exp','_int','_sign')
+ __slots__ = ('_exp','_int','_sign', '_is_special')
+ # Generally, the value of the Decimal instance is given by
+ # (-1)**_sign * _int * 10**_exp
+ # Special values are signified by _is_special == True
- def __init__(self, value="0", context=None):
+ # We're immutable, so use _new__ not __init__
+ def __new__(cls, value="0", context=None):
"""Create a decimal point instance.
>>> Decimal('3.14') # string input
@@ -468,95 +472,99 @@ class Decimal(object):
>>> Decimal(Decimal(314)) # another decimal instance
Decimal("314")
"""
- if context is None:
- context = getcontext()
+ self = object.__new__(cls)
+ self._is_special = False
+
+ # From an internal working value
+ if isinstance(value, _WorkRep):
+ if value.sign == 1:
+ self._sign = 0
+ else:
+ self._sign = 1
+ self._int = tuple(map(int, str(value.int)))
+ self._exp = int(value.exp)
+ return self
+
+ # From another decimal
+ if isinstance(value, Decimal):
+ self._exp = value._exp
+ self._sign = value._sign
+ self._int = value._int
+ self._is_special = value._is_special
+ return self
+
+ # From an integer
if isinstance(value, (int,long)):
- value = str(value)
+ if value >= 0:
+ self._sign = 0
+ else:
+ self._sign = 1
+ self._exp = 0
+ self._int = tuple(map(int, str(abs(value))))
+ return self
+
+ # tuple/list conversion (possibly from as_tuple())
+ if isinstance(value, (list,tuple)):
+ if len(value) != 3:
+ raise ValueError, 'Invalid arguments'
+ if value[0] not in [0,1]:
+ raise ValueError, 'Invalid sign'
+ for digit in value[1]:
+ if not isinstance(digit, (int,long)) or digit < 0:
+ raise ValueError, "The second value in the tuple must be composed of non negative integer elements."
+
+ self._sign = value[0]
+ self._int = tuple(value[1])
+ if value[2] in ('F','n','N'):
+ self._exp = value[2]
+ self._is_special = True
+ else:
+ self._exp = int(value[2])
+ return self
+
+ if isinstance(value, float):
+ raise TypeError("Cannot convert float to Decimal. " +
+ "First convert the float to a string")
- # String?
+ # Other argument types may require the context during interpretation
+ if context is None:
+ context = getcontext()
+
+ # From a string
# REs insist on real strings, so we can too.
if isinstance(value, basestring):
if _isinfinity(value):
self._exp = 'F'
self._int = (0,)
- sign = _isinfinity(value)
- if sign == 1:
+ self._is_special = True
+ if _isinfinity(value) == 1:
self._sign = 0
else:
self._sign = 1
- return
+ return self
if _isnan(value):
sig, sign, diag = _isnan(value)
+ self._is_special = True
if len(diag) > context.prec: #Diagnostic info too long
self._sign, self._int, self._exp = \
context._raise_error(ConversionSyntax)
- return
+ return self
if sig == 1:
self._exp = 'n' #qNaN
else: #sig == 2
self._exp = 'N' #sNaN
self._sign = sign
self._int = tuple(map(int, diag)) #Diagnostic info
- return
+ return self
try:
self._sign, self._int, self._exp = _string2exact(value)
except ValueError:
+ self._is_special = True
self._sign, self._int, self._exp = context._raise_error(ConversionSyntax)
- return
-
- # tuple/list conversion (possibly from as_tuple())
- if isinstance(value, (list,tuple)):
- if len(value) != 3:
- raise ValueError, 'Invalid arguments'
- if value[0] not in [0,1]:
- raise ValueError, 'Invalid sign'
- for digit in value[1]:
- if not isinstance(digit, (int,long)) or digit < 0:
- raise ValueError, "The second value in the tuple must be composed of non negative integer elements."
-
- self._sign = value[0]
- self._int = tuple(value[1])
- if value[2] in ('F','n','N'):
- self._exp = value[2]
- else:
- self._exp = int(value[2])
- return
-
- # Turn an intermediate value back to Decimal()
- if isinstance(value, _WorkRep):
- if value.sign == 1:
- self._sign = 0
- else:
- self._sign = 1
- self._int = tuple(value.int)
- self._exp = int(value.exp)
- return
-
- if isinstance(value, Decimal):
- self._exp = value._exp
- self._sign = value._sign
- self._int = value._int
- return
-
- if isinstance(value, float):
- raise TypeError("Cannot convert float to Decimal. " +
- "First convert the float to a string")
-
- raise TypeError("Cannot convert %r" % value)
-
- def _convert_other(self, other):
- """Convert other to Decimal.
-
- Verifies that it's ok to use in an implicit construction.
- """
- if isinstance(other, Decimal):
- return other
- if isinstance(other, (int, long)):
- other = Decimal(other)
- return other
+ return self
- raise TypeError, "You can interact Decimal only with int, long or Decimal data types."
+ raise TypeError("Cannot convert %r to Decimal" % value)
def _isnan(self):
"""Returns whether the number is not actually one.
@@ -565,10 +573,12 @@ class Decimal(object):
1 if NaN
2 if sNaN
"""
- if self._exp == 'n':
- return 1
- elif self._exp == 'N':
- return 2
+ if self._is_special:
+ exp = self._exp
+ if exp == 'n':
+ return 1
+ elif exp == 'N':
+ return 2
return 0
def _isinfinity(self):
@@ -593,18 +603,26 @@ class Decimal(object):
Done before operations.
"""
- if context is None:
- context = getcontext()
- if self._isnan() == 2:
- return context._raise_error(InvalidOperation, 'sNaN',
- 1, self)
- if other is not None and other._isnan() == 2:
- return context._raise_error(InvalidOperation, 'sNaN',
- 1, other)
- if self._isnan():
- return self
- if other is not None and other._isnan():
+ self_is_nan = self._isnan()
+ if other is None:
+ other_is_nan = False
+ else:
+ other_is_nan = other._isnan()
+
+ if self_is_nan or other_is_nan:
+ if context is None:
+ context = getcontext()
+
+ if self_is_nan == 2:
+ return context._raise_error(InvalidOperation, 'sNaN',
+ 1, self)
+ if other_is_nan == 2:
+ return context._raise_error(InvalidOperation, 'sNaN',
+ 1, other)
+ if self_is_nan:
+ return self
+
return other
return 0
@@ -614,18 +632,20 @@ class Decimal(object):
0 if self == 0
1 if self != 0
"""
- if isinstance(self._exp, str):
+ if self._is_special:
return 1
- return self._int != (0,)*len(self._int)
+ return sum(self._int) != 0
def __cmp__(self, other, context=None):
- if context is None:
- context = getcontext()
- other = self._convert_other(other)
+ other = _convert_other(other)
- ans = self._check_nans(other, context)
- if ans:
- return 1
+ if self._is_special or other._is_special:
+ ans = self._check_nans(other, context)
+ if ans:
+ return 1 # Comparison involving NaN's always reports self > other
+
+ # INF = INF
+ return cmp(self._isinfinity(), other._isinfinity())
if not self and not other:
return 0 #If both 0, sign comparison isn't certain.
@@ -636,23 +656,21 @@ class Decimal(object):
if self._sign < other._sign:
return 1
- # INF = INF
- if self._isinfinity() and other._isinfinity():
- return 0
- if self._isinfinity():
- return (-1)**self._sign
- if other._isinfinity():
- return -((-1)**other._sign)
-
- if self.adjusted() == other.adjusted() and \
+ self_adjusted = self.adjusted()
+ other_adjusted = other.adjusted()
+ if self_adjusted == other_adjusted and \
self._int + (0,)*(self._exp - other._exp) == \
other._int + (0,)*(other._exp - self._exp):
return 0 #equal, except in precision. ([0]*(-x) = [])
- elif self.adjusted() > other.adjusted() and self._int[0] != 0:
+ elif self_adjusted > other_adjusted and self._int[0] != 0:
return (-1)**self._sign
- elif self.adjusted < other.adjusted() and other._int[0] != 0:
+ elif self_adjusted < other_adjusted and other._int[0] != 0:
return -((-1)**self._sign)
+ # Need to round, so make sure we have a valid context
+ if context is None:
+ context = getcontext()
+
context = context._shallow_copy()
rounding = context._set_rounding(ROUND_UP) #round away from 0
@@ -688,14 +706,13 @@ class Decimal(object):
NaN => one is NaN
Like __cmp__, but returns Decimal instances.
"""
- if context is None:
- context = getcontext()
- other = self._convert_other(other)
+ other = _convert_other(other)
#compare(NaN, NaN) = NaN
- ans = self._check_nans(other, context)
- if ans:
- return ans
+ if (self._is_special or other and other._is_special):
+ ans = self._check_nans(other, context)
+ if ans:
+ return ans
return Decimal(self.__cmp__(other, context))
@@ -811,8 +828,6 @@ class Decimal(object):
Same rules for when in exponential and when as a value as in __str__.
"""
- if context is None:
- context = getcontext()
return self.__str__(eng=1, context=context)
def __neg__(self, context=None):
@@ -820,11 +835,10 @@ class Decimal(object):
Rounds, if it has reason.
"""
- if context is None:
- context = getcontext()
- ans = self._check_nans(context=context)
- if ans:
- return ans
+ if self._is_special:
+ ans = self._check_nans(context=context)
+ if ans:
+ return ans
if not self:
# -Decimal('0') is Decimal('0'), not Decimal('-0')
@@ -833,6 +847,9 @@ class Decimal(object):
sign = 0
else:
sign = 1
+
+ if context is None:
+ context = getcontext()
if context._rounding_decision == ALWAYS_ROUND:
return Decimal((sign, self._int, self._exp))._fix(context=context)
return Decimal( (sign, self._int, self._exp))
@@ -842,17 +859,19 @@ class Decimal(object):
Rounds the number (if more then precision digits)
"""
- if context is None:
- context = getcontext()
- ans = self._check_nans(context=context)
- if ans:
- return ans
+ if self._is_special:
+ ans = self._check_nans(context=context)
+ if ans:
+ return ans
sign = self._sign
if not self:
# + (-0) = 0
sign = 0
+ if context is None:
+ context = getcontext()
+
if context._rounding_decision == ALWAYS_ROUND:
ans = self._fix(context=context)
else:
@@ -865,13 +884,14 @@ class Decimal(object):
If the second argument is 0, do not round.
"""
- if context is None:
- context = getcontext()
- ans = self._check_nans(context=context)
- if ans:
- return ans
+ if self._is_special:
+ ans = self._check_nans(context=context)
+ if ans:
+ return ans
if not round:
+ if context is None:
+ context = getcontext()
context = context._shallow_copy()
context._set_rounding_decision(NEVER_ROUND)
@@ -887,21 +907,23 @@ class Decimal(object):
-INF + INF (or the reverse) cause InvalidOperation errors.
"""
+ other = _convert_other(other)
+
if context is None:
context = getcontext()
- other = self._convert_other(other)
- ans = self._check_nans(other, context)
- if ans:
- return ans
+ if self._is_special or other._is_special:
+ ans = self._check_nans(other, context)
+ if ans:
+ return ans
- if self._isinfinity():
- #If both INF, same sign => same as both, opposite => error.
- if self._sign != other._sign and other._isinfinity():
- return context._raise_error(InvalidOperation, '-INF + INF')
- return Decimal(self)
- if other._isinfinity():
- return Decimal(other) #Can't both be infinity here
+ if self._isinfinity():
+ #If both INF, same sign => same as both, opposite => error.
+ if self._sign != other._sign and other._isinfinity():
+ return context._raise_error(InvalidOperation, '-INF + INF')
+ return Decimal(self)
+ if other._isinfinity():
+ return Decimal(other) #Can't both be infinity here
shouldround = context._rounding_decision == ALWAYS_ROUND
@@ -961,36 +983,10 @@ class Decimal(object):
result.sign = 1
#Now, op1 > abs(op2) > 0
- op1.int.reverse()
- op2.int.reverse()
-
if op2.sign == 1:
- result.int = resultint = map(operator.add, op1.int, op2.int)
- carry = 0
- for i in xrange(len(op1.int)):
- tmp = resultint[i] + carry
- carry = 0
- if tmp > 9:
- carry = 1
- tmp -= 10
- resultint[i] = tmp
- if carry:
- resultint.append(1)
+ result.int = op1.int + op2.int
else:
- result.int = resultint = map(operator.sub, op1.int, op2.int)
- loan = 0
- for i in xrange(len(op1.int)):
- tmp = resultint[i] - loan
- loan = 0
- if tmp < 0:
- loan = 1
- tmp += 10
- resultint[i] = tmp
- assert not loan
-
- while resultint[-1] == 0:
- resultint.pop()
- resultint.reverse()
+ result.int = op1.int - op2.int
result.exp = op1.exp
ans = Decimal(result)
@@ -1002,13 +998,12 @@ class Decimal(object):
def __sub__(self, other, context=None):
"""Return self + (-other)"""
- if context is None:
- context = getcontext()
- other = self._convert_other(other)
+ other = _convert_other(other)
- ans = self._check_nans(other, context=context)
- if ans:
- return ans
+ if self._is_special or other._is_special:
+ ans = self._check_nans(other, context=context)
+ if ans:
+ return ans
# -Decimal(0) = Decimal(0), which we don't want since
# (-0 - 0 = -0 + (-0) = -0, but -0 + 0 = 0.)
@@ -1020,9 +1015,7 @@ class Decimal(object):
def __rsub__(self, other, context=None):
"""Return other + (-self)"""
- if context is None:
- context = getcontext()
- other = self._convert_other(other)
+ other = _convert_other(other)
tmp = Decimal(self)
tmp._sign = 1 - tmp._sign
@@ -1037,11 +1030,12 @@ class Decimal(object):
For example:
Decimal('5.624e10')._increment() == Decimal('5.625e10')
"""
- if context is None:
- context = getcontext()
- ans = self._check_nans(context=context)
- if ans:
- return ans
+ if self._is_special:
+ ans = self._check_nans(context=context)
+ if ans:
+ return ans
+
+ return Decimal(self) # Must be infinite, and incrementing makes no difference
L = list(self._int)
L[-1] += 1
@@ -1055,6 +1049,8 @@ class Decimal(object):
spot -= 1
ans = Decimal((self._sign, L, self._exp))
+ if context is None:
+ context = getcontext()
if round and context._rounding_decision == ALWAYS_ROUND:
ans = ans._fix(context=context)
return ans
@@ -1064,24 +1060,27 @@ class Decimal(object):
(+-) INF * 0 (or its reverse) raise InvalidOperation.
"""
+ other = _convert_other(other)
+
if context is None:
context = getcontext()
- other = self._convert_other(other)
-
- ans = self._check_nans(other, context)
- if ans:
- return ans
resultsign = self._sign ^ other._sign
- if self._isinfinity():
- if not other:
- return context._raise_error(InvalidOperation, '(+-)INF * 0')
- return Infsign[resultsign]
- if other._isinfinity():
- if not self:
- return context._raise_error(InvalidOperation, '0 * (+-)INF')
- return Infsign[resultsign]
+ if self._is_special or other._is_special:
+ ans = self._check_nans(other, context)
+ if ans:
+ return ans
+
+ if self._isinfinity():
+ if not other:
+ return context._raise_error(InvalidOperation, '(+-)INF * 0')
+ return Infsign[resultsign]
+
+ if other._isinfinity():
+ if not self:
+ return context._raise_error(InvalidOperation, '0 * (+-)INF')
+ return Infsign[resultsign]
resultexp = self._exp + other._exp
shouldround = context._rounding_decision == ALWAYS_ROUND
@@ -1106,32 +1105,10 @@ class Decimal(object):
ans = ans._fix(context=context)
return ans
- op1 = list(self._int)
- op2 = list(other._int)
- op1.reverse()
- op2.reverse()
- # Minimize Decimal additions
- if len(op2) > len(op1):
- op1, op2 = op2, op1
-
- _divmod = divmod
- accumulator = [0]*(len(self._int) + len(other._int))
- for i in xrange(len(op2)):
- if op2[i] == 0:
- continue
- mult = op2[i]
- carry = 0
- for j in xrange(len(op1)):
- carry, accumulator[i+j] = _divmod( mult * op1[j] + carry
- + accumulator[i+j], 10)
-
- if carry:
- accumulator[i + j + 1] += carry
- while not accumulator[-1]:
- accumulator.pop()
- accumulator.reverse()
-
- ans = Decimal( (resultsign, accumulator, resultexp))
+ op1 = _WorkRep(self)
+ op2 = _WorkRep(other)
+
+ ans = Decimal( (resultsign, map(int, str(op1.int * op2.int)), resultexp))
if shouldround:
ans = ans._fix(context=context)
@@ -1155,53 +1132,28 @@ class Decimal(object):
Actually, if divmod is 2 or 3 a tuple is returned, but errors for
computing the other value are not raised.
"""
+ other = _convert_other(other)
+
if context is None:
context = getcontext()
- other = self._convert_other(other)
- ans = self._check_nans(other, context)
- if ans:
- if divmod:
- return (ans, ans)
- else:
+ sign = self._sign ^ other._sign
+
+ if self._is_special or other._is_special:
+ ans = self._check_nans(other, context)
+ if ans:
+ if divmod:
+ return (ans, ans)
return ans
- sign = self._sign ^ other._sign
- if not self and not other:
- if divmod:
- return context._raise_error(DivisionUndefined, '0 / 0', 1)
- return context._raise_error(DivisionUndefined, '0 / 0')
- if self._isinfinity() and other._isinfinity():
- if not divmod:
+ if self._isinfinity() and other._isinfinity():
+ if divmod:
+ return (context._raise_error(InvalidOperation,
+ '(+-)INF // (+-)INF'),
+ context._raise_error(InvalidOperation,
+ '(+-)INF % (+-)INF'))
return context._raise_error(InvalidOperation, '(+-)INF/(+-)INF')
- else:
- return (context._raise_error(InvalidOperation,
- '(+-)INF // (+-)INF'),
- context._raise_error(InvalidOperation,
- '(+-)INF % (+-)INF'))
- if not divmod:
- if other._isinfinity():
- context._raise_error(Clamped, 'Division by infinity')
- return Decimal((sign, (0,), context.Etiny()))
- if self._isinfinity():
- return Infsign[sign]
- #These two have different precision.
- if not self:
- exp = self._exp - other._exp
- if exp < context.Etiny():
- exp = context.Etiny()
- context._raise_error(Clamped, '0e-x / y')
- if exp > context.Emax:
- exp = context.Emax
- context._raise_error(Clamped, '0e+x / y')
- return Decimal( (sign, (0,), exp) )
-
- if not other:
- return context._raise_error(DivisionByZero, 'x / 0', sign)
- if divmod:
- if other._isinfinity():
- return (Decimal((sign, (0,), 0)), Decimal(self))
if self._isinfinity():
if divmod == 1:
return (Infsign[sign],
@@ -1211,16 +1163,41 @@ class Decimal(object):
elif divmod == 3:
return (Infsign[sign],
context._raise_error(InvalidOperation, 'INF % x'))
- if not self:
+ return Infsign[sign]
+
+ if other._isinfinity():
+ if divmod:
+ return (Decimal((sign, (0,), 0)), Decimal(self))
+ context._raise_error(Clamped, 'Division by infinity')
+ return Decimal((sign, (0,), context.Etiny()))
+
+ # Special cases for zeroes
+ if not self and not other:
+ if divmod:
+ return context._raise_error(DivisionUndefined, '0 / 0', 1)
+ return context._raise_error(DivisionUndefined, '0 / 0')
+
+ if not self:
+ if divmod:
otherside = Decimal(self)
otherside._exp = min(self._exp, other._exp)
return (Decimal((sign, (0,), 0)), otherside)
+ exp = self._exp - other._exp
+ if exp < context.Etiny():
+ exp = context.Etiny()
+ context._raise_error(Clamped, '0e-x / y')
+ if exp > context.Emax:
+ exp = context.Emax
+ context._raise_error(Clamped, '0e+x / y')
+ return Decimal( (sign, (0,), exp) )
- if not other:
+ if not other:
+ if divmod:
return context._raise_error(DivisionByZero, 'divmod(x,0)',
sign, 1)
+ return context._raise_error(DivisionByZero, 'x / 0', sign)
- #OK, so neither = 0, INF
+ #OK, so neither = 0, INF or NaN
shouldround = context._rounding_decision == ALWAYS_ROUND
@@ -1248,19 +1225,18 @@ class Decimal(object):
op1 = _WorkRep(self)
op2 = _WorkRep(other)
op1, op2, adjust = _adjust_coefficients(op1, op2)
- res = _WorkRep( (sign, [0], (op1.exp - op2.exp)) )
+ res = _WorkRep( (sign, 0, (op1.exp - op2.exp)) )
if divmod and res.exp > context.prec + 1:
return context._raise_error(DivisionImpossible)
ans = None
+ prec_limit = 10 ** context.prec
while 1:
- while( (len(op2.int) < len(op1.int) and op1.int[0]) or
- (len(op2.int) == len(op1.int) and op2.int <= op1.int)):
- #Meaning, while op2.int < op1.int, when normalized.
- res._increment()
- op1.subtract(op2.int)
+ while op2.int <= op1.int:
+ res.int += 1
+ op1.int -= op2.int
if res.exp == 0 and divmod:
- if len(res.int) > context.prec and shouldround:
+ if res.int >= prec_limit and shouldround:
return context._raise_error(DivisionImpossible)
otherside = Decimal(op1)
frozen = context._ignore_all_flags()
@@ -1273,31 +1249,30 @@ class Decimal(object):
otherside = otherside._fix(context=context)
return (Decimal(res), otherside)
- if op1.int == [0]*len(op1.int) and adjust >= 0 and not divmod:
+ if op1.int == 0 and adjust >= 0 and not divmod:
break
- if (len(res.int) > context.prec) and shouldround:
+ if res.int >= prec_limit and shouldround:
if divmod:
return context._raise_error(DivisionImpossible)
shouldround=1
# Really, the answer is a bit higher, so adding a one to
# the end will make sure the rounding is right.
- if op1.int != [0]*len(op1.int):
- res.int.append(1)
+ if op1.int != 0:
+ res.int *= 10
+ res.int += 1
res.exp -= 1
break
+ res.int *= 10
res.exp -= 1
adjust += 1
- res.int.append(0)
- op1.int.append(0)
+ op1.int *= 10
op1.exp -= 1
- if res.exp == 0 and divmod and (len(op2.int) > len(op1.int) or
- (len(op2.int) == len(op1.int) and
- op2.int > op1.int)):
+ if res.exp == 0 and divmod and op2.int > op1.int:
#Solves an error in precision. Same as a previous block.
- if len(res.int) > context.prec and shouldround:
+ if res.int >= prec_limit and shouldround:
return context._raise_error(DivisionImpossible)
otherside = Decimal(op1)
frozen = context._ignore_all_flags()
@@ -1316,7 +1291,7 @@ class Decimal(object):
def __rdiv__(self, other, context=None):
"""Swaps self/other and returns __div__."""
- other = self._convert_other(other)
+ other = _convert_other(other)
return other.__div__(self, context=context)
__rtruediv__ = __rdiv__
@@ -1328,20 +1303,19 @@ class Decimal(object):
def __rdivmod__(self, other, context=None):
"""Swaps self/other and returns __divmod__."""
- other = self._convert_other(other)
+ other = _convert_other(other)
return other.__divmod__(self, context=context)
def __mod__(self, other, context=None):
"""
self % other
"""
- if context is None:
- context = getcontext()
- other = self._convert_other(other)
+ other = _convert_other(other)
- ans = self._check_nans(other, context)
- if ans:
- return ans
+ if self._is_special or other._is_special:
+ ans = self._check_nans(other, context)
+ if ans:
+ return ans
if self and not other:
return context._raise_error(InvalidOperation, 'x % 0')
@@ -1350,23 +1324,24 @@ class Decimal(object):
def __rmod__(self, other, context=None):
"""Swaps self/other and returns __mod__."""
- other = self._convert_other(other)
+ other = _convert_other(other)
return other.__mod__(self, context=context)
def remainder_near(self, other, context=None):
"""
Remainder nearest to 0- abs(remainder-near) <= other/2
"""
- if context is None:
- context = getcontext()
- other = self._convert_other(other)
+ other = _convert_other(other)
- ans = self._check_nans(other, context)
- if ans:
- return ans
+ if self._is_special or other._is_special:
+ ans = self._check_nans(other, context)
+ if ans:
+ return ans
if self and not other:
return context._raise_error(InvalidOperation, 'x % 0')
+ if context is None:
+ context = getcontext()
# If DivisionImpossible causes an error, do not leave Rounded/Inexact
# ignored in the calling function.
context = context._shallow_copy()
@@ -1435,7 +1410,7 @@ class Decimal(object):
def __rfloordiv__(self, other, context=None):
"""Swaps self/other and returns __floordiv__."""
- other = self._convert_other(other)
+ other = _convert_other(other)
return other.__floordiv__(self, context=context)
def __float__(self):
@@ -1444,11 +1419,12 @@ class Decimal(object):
def __int__(self):
"""Converts self to a int, truncating if necessary."""
- if self._isnan():
- context = getcontext()
- return context._raise_error(InvalidContext)
- elif self._isinfinity():
- raise OverflowError, "Cannot convert infinity to long"
+ if self._is_special:
+ if self._isnan():
+ context = getcontext()
+ return context._raise_error(InvalidContext)
+ elif self._isinfinity():
+ raise OverflowError, "Cannot convert infinity to long"
if not self:
return 0
sign = '-'*self._sign
@@ -1457,13 +1433,6 @@ class Decimal(object):
return int(s)
s = sign + ''.join(map(str, self._int))[:self._exp]
return int(s)
- tmp = list(self._int)
- tmp.reverse()
- val = 0
- while tmp:
- val *= 10
- val += tmp.pop()
- return int(((-1) ** self._sign) * val * 10.**int(self._exp))
def __long__(self):
"""Converts to a long.
@@ -1484,7 +1453,7 @@ class Decimal(object):
folddown - Fold down high elements, by default context._clamp
context - context used.
"""
- if self._isinfinity() or self._isnan():
+ if self._is_special:
return self
if context is None:
context = getcontext()
@@ -1495,14 +1464,14 @@ class Decimal(object):
context=context)
if len(ans._int) > prec:
ans = ans._round(prec, rounding, context=context)
- ans = ans._fixexponents(prec, rounding, folddown=folddown,
- context=context)
+ ans = ans._fixexponents(prec, rounding, folddown=folddown,
+ context=context)
return ans
def _fixexponents(self, prec=None, rounding=None, folddown=None,
context=None):
"""Fix the exponents and return a copy with the exponent in bounds."""
- if self._isinfinity():
+ if self._is_special:
return self
if context is None:
context = getcontext()
@@ -1510,10 +1479,10 @@ class Decimal(object):
prec = context.prec
if folddown is None:
folddown = context._clamp
- Emin, Emax = context.Emin, context.Emax
- Etop = context.Etop()
+ Emin = context.Emin
ans = Decimal(self)
- if ans.adjusted() < Emin:
+ ans_adjusted = ans.adjusted()
+ if ans_adjusted < Emin:
Etiny = context.Etiny()
if ans._exp < Etiny:
if not ans:
@@ -1529,17 +1498,21 @@ class Decimal(object):
if ans:
#Only raise subnormal if non-zero.
context._raise_error(Subnormal)
- elif folddown and ans._exp > Etop:
- context._raise_error(Clamped)
- ans = ans._rescale(Etop, context=context)
- elif ans.adjusted() > Emax:
- if not ans:
- ans._exp = Emax
+ else:
+ Etop = context.Etop()
+ if folddown and ans._exp > Etop:
context._raise_error(Clamped)
- return ans
- context._raise_error(Inexact)
- context._raise_error(Rounded)
- return context._raise_error(Overflow, 'above Emax', ans._sign)
+ ans = ans._rescale(Etop, context=context)
+ else:
+ Emax = context.Emax
+ if ans_adjusted > Emax:
+ if not ans:
+ ans._exp = Emax
+ context._raise_error(Clamped)
+ return ans
+ context._raise_error(Inexact)
+ context._raise_error(Rounded)
+ return context._raise_error(Overflow, 'above Emax', ans._sign)
return ans
def _round(self, prec=None, rounding=None, context=None):
@@ -1549,14 +1522,17 @@ class Decimal(object):
context determines it.
"""
+
+ if self._is_special:
+ ans = self._check_nans(context=context)
+ if ans:
+ return ans
+
+ if self._isinfinity():
+ return Decimal(self)
+
if context is None:
context = getcontext()
- ans = self._check_nans(context=context)
- if ans:
- return ans
-
- if self._isinfinity():
- return Decimal(self)
if rounding is None:
rounding = context.rounding
@@ -1696,19 +1672,21 @@ class Decimal(object):
If modulo is None (default), don't take it mod modulo.
"""
+ n = _convert_other(n)
+
if context is None:
context = getcontext()
- n = self._convert_other(n)
- #Because the spot << doesn't work with really big exponents
- if n._isinfinity() or n.adjusted() > 8:
- return context._raise_error(InvalidOperation, 'x ** INF')
+ if self._is_special or n._is_special or n.adjusted() > 8:
+ #Because the spot << doesn't work with really big exponents
+ if n._isinfinity() or n.adjusted() > 8:
+ return context._raise_error(InvalidOperation, 'x ** INF')
- ans = self._check_nans(n, context)
- if ans:
- return ans
+ ans = self._check_nans(n, context)
+ if ans:
+ return ans
- if not n._isinfinity() and not n._isinteger():
+ if not n._isinteger():
return context._raise_error(InvalidOperation, 'x ** (non-integer)')
if not self and not n:
@@ -1783,17 +1761,16 @@ class Decimal(object):
def __rpow__(self, other, context=None):
"""Swaps self/other and returns __pow__."""
- other = self._convert_other(other)
+ other = _convert_other(other)
return other.__pow__(self, context=context)
def normalize(self, context=None):
"""Normalize- strip trailing 0s, change anything equal to 0 to 0e0"""
- if context is None:
- context = getcontext()
- ans = self._check_nans(context=context)
- if ans:
- return ans
+ if self._is_special:
+ ans = self._check_nans(context=context)
+ if ans:
+ return ans
dup = self._fix(context=context)
if dup._isinfinity():
@@ -1814,18 +1791,18 @@ class Decimal(object):
Similar to self._rescale(exp._exp) but with error checking.
"""
- if context is None:
- context = getcontext()
-
- ans = self._check_nans(exp, context)
- if ans:
- return ans
+ if self._is_special or exp._is_special:
+ ans = self._check_nans(exp, context)
+ if ans:
+ return ans
- if exp._isinfinity() or self._isinfinity():
- if exp._isinfinity() and self._isinfinity():
- return self #if both are inf, it is OK
- return context._raise_error(InvalidOperation,
- 'quantize with one INF')
+ if exp._isinfinity() or self._isinfinity():
+ if exp._isinfinity() and self._isinfinity():
+ return self #if both are inf, it is OK
+ if context is None:
+ context = getcontext()
+ return context._raise_error(InvalidOperation,
+ 'quantize with one INF')
return self._rescale(exp._exp, rounding, context, watchexp)
def same_quantum(self, other):
@@ -1833,10 +1810,11 @@ class Decimal(object):
same as self._exp == other._exp, except NaN == sNaN
"""
- if self._isnan() or other._isnan():
- return self._isnan() and other._isnan() and True
- if self._isinfinity() or other._isinfinity():
- return self._isinfinity() and other._isinfinity() and True
+ if self._is_special or other._is_special:
+ if self._isnan() or other._isnan():
+ return self._isnan() and other._isnan() and True
+ if self._isinfinity() or other._isinfinity():
+ return self._isinfinity() and other._isinfinity() and True
return self._exp == other._exp
def _rescale(self, exp, rounding = None, context=None, watchexp = 1):
@@ -1850,12 +1828,13 @@ class Decimal(object):
if context is None:
context = getcontext()
- if self._isinfinity():
- return context._raise_error(InvalidOperation, 'rescale with an INF')
+ if self._is_special:
+ if self._isinfinity():
+ return context._raise_error(InvalidOperation, 'rescale with an INF')
- ans = self._check_nans(context=context)
- if ans:
- return ans
+ ans = self._check_nans(context=context)
+ if ans:
+ return ans
out = 0
@@ -1889,21 +1868,23 @@ class Decimal(object):
tmp._int = tmp._int[1:]
tmp._exp = exp
- if tmp and tmp.adjusted() < context.Emin:
+ tmp_adjusted = tmp.adjusted()
+ if tmp and tmp_adjusted < context.Emin:
context._raise_error(Subnormal)
- elif tmp and tmp.adjusted() > context.Emax:
+ elif tmp and tmp_adjusted > context.Emax:
return context._raise_error(InvalidOperation, 'rescale(a, INF)')
return tmp
def to_integral(self, rounding = None, context=None):
"""Rounds to the nearest integer, without raising inexact, rounded."""
- if context is None:
- context = getcontext()
- ans = self._check_nans(context=context)
- if ans:
- return ans
+ if self._is_special:
+ ans = self._check_nans(context=context)
+ if ans:
+ return ans
if self._exp >= 0:
return self
+ if context is None:
+ context = getcontext()
flags = context._ignore_flags(Rounded, Inexact)
ans = self._rescale(0, rounding, context=context)
context._regard_flags(flags)
@@ -1915,12 +1896,13 @@ class Decimal(object):
Uses a converging algorithm (Xn+1 = 0.5*(Xn + self / Xn))
Should quadratically approach the right answer.
"""
- if context is None:
- context = getcontext()
+ if self._is_special:
+ ans = self._check_nans(context=context)
+ if ans:
+ return ans
- ans = self._check_nans(context=context)
- if ans:
- return ans
+ if self._isinfinity() and self._sign == 0:
+ return Decimal(self)
if not self:
#exponent = self._exp / 2, using round_down.
@@ -1934,12 +1916,12 @@ class Decimal(object):
else:
return Decimal( (0, (0,), exp))
+ if context is None:
+ context = getcontext()
+
if self._sign == 1:
return context._raise_error(InvalidOperation, 'sqrt(-x), x > 0')
- if self._isinfinity():
- return Decimal(self)
-
tmp = Decimal(self)
expadd = tmp._exp / 2
@@ -2037,20 +2019,19 @@ class Decimal(object):
like max(self, other) except if one is not a number, returns
NaN (and signals if one is sNaN). Also rounds.
"""
- if context is None:
- context = getcontext()
- other = self._convert_other(other)
-
- # if one operand is a quiet NaN and the other is number, then the
- # number is always returned
- sn = self._isnan()
- on = other._isnan()
- if sn or on:
- if on == 1 and sn != 2:
- return self
- if sn == 1 and on != 2:
- return other
- return self._check_nans(other, context)
+ other = _convert_other(other)
+
+ if self._is_special or other._is_special:
+ # if one operand is a quiet NaN and the other is number, then the
+ # number is always returned
+ sn = self._isnan()
+ on = other._isnan()
+ if sn or on:
+ if on == 1 and sn != 2:
+ return self
+ if sn == 1 and on != 2:
+ return other
+ return self._check_nans(other, context)
ans = self
c = self.__cmp__(other)
@@ -2072,6 +2053,9 @@ class Decimal(object):
ans = other
elif c == -1:
ans = other
+
+ if context is None:
+ context = getcontext()
context._rounding_decision == ALWAYS_ROUND
return ans._fix(context=context)
@@ -2081,20 +2065,19 @@ class Decimal(object):
like min(self, other) except if one is not a number, returns
NaN (and signals if one is sNaN). Also rounds.
"""
- if context is None:
- context = getcontext()
- other = self._convert_other(other)
-
- # if one operand is a quiet NaN and the other is number, then the
- # number is always returned
- sn = self._isnan()
- on = other._isnan()
- if sn or on:
- if on == 1 and sn != 2:
- return self
- if sn == 1 and on != 2:
- return other
- return self._check_nans(other, context)
+ other = _convert_other(other)
+
+ if self._is_special or other._is_special:
+ # if one operand is a quiet NaN and the other is number, then the
+ # number is always returned
+ sn = self._isnan()
+ on = other._isnan()
+ if sn or on:
+ if on == 1 and sn != 2:
+ return self
+ if sn == 1 and on != 2:
+ return other
+ return self._check_nans(other, context)
ans = self
c = self.__cmp__(other)
@@ -2116,6 +2099,9 @@ class Decimal(object):
ans = other
elif c == 1:
ans = other
+
+ if context is None:
+ context = getcontext()
context._rounding_decision == ALWAYS_ROUND
return ans._fix(context=context)
@@ -2801,20 +2787,23 @@ class Context(object):
class _WorkRep(object):
__slots__ = ('sign','int','exp')
# sign: -1 None 1
- # int: list
+ # int: int or long
# exp: None, int, or string
def __init__(self, value=None):
if value is None:
self.sign = None
- self.int = []
+ self.int = 0
self.exp = None
if isinstance(value, Decimal):
if value._sign:
self.sign = -1
else:
self.sign = 1
- self.int = list(value._int)
+ cum = 0
+ for digit in value._int:
+ cum = cum * 10 + digit
+ self.int = cum
self.exp = value._exp
if isinstance(value, tuple):
self.sign = value[0]
@@ -2852,59 +2841,12 @@ class _WorkRep(object):
direction = 1
int1 = self.int
int2 = other.int
- if len(int1) > len(int2):
+ if int1 > int2:
return direction * 1
- if len(int1) < len(int2):
+ if int1 < int2:
return direction * -1
- for i in xrange(len(int1)):
- if int1[i] > int2[i]:
- return direction * 1
- if int1[i] < int2[i]:
- return direction * -1
return 0
- def _increment(self):
- curspot = len(self.int) - 1
- self.int[curspot]+= 1
- while (self.int[curspot] >= 10):
- self.int[curspot] -= 10
- if curspot == 0:
- self.int[0:0] = [1]
- break
- self.int[curspot-1] += 1
- curspot -= 1
-
- def subtract(self, alist):
- """Subtract a list from the current int (in place).
-
- It is assured that (len(list) = len(self.int) and list < self.int) or
- len(list) = len(self.int)-1
- (i.e. that int(join(list)) < int(join(self.int)))
- """
-
- selfint = self.int
- selfint.reverse()
- alist.reverse()
-
- carry = 0
- for x in xrange(len(alist)):
- selfint[x] -= alist[x] + carry
- if selfint[x] < 0:
- carry = 1
- selfint[x] += 10
- else:
- carry = 0
- if carry:
- selfint[x+1] -= 1
- last = len(selfint)-1
- while len(selfint) > 1 and selfint[last] == 0:
- last -= 1
- if last == 0:
- break
- selfint[last+1:]=[]
- selfint.reverse()
- alist.reverse()
- return
def _normalize(op1, op2, shouldround = 0, prec = 0):
@@ -2923,70 +2865,65 @@ def _normalize(op1, op2, shouldround = 0, prec = 0):
tmp = op1
other = op2
- if shouldround and numdigits > len(other.int) + prec + 1 -len(tmp.int):
- # If the difference in adjusted exps is > prec+1, we know
- # other is insignificant, so might as well put a 1 after the precision.
- # (since this is only for addition.) Also stops MemoryErrors.
-
- extend = prec + 2 -len(tmp.int)
- if extend <= 0:
- extend = 1
- tmp.int.extend([0]*extend)
- tmp.exp -= extend
- other.int[:] = [0]*(len(tmp.int)-1)+[1]
- other.exp = tmp.exp
- return op1, op2
-
- tmp.int.extend([0] * numdigits)
- tmp.exp = tmp.exp - numdigits
- numdigits = len(op1.int) - len(op2.int)
- # numdigits != 0 => They have the same exponent, but not the same length
- # of the coefficient.
- if numdigits < 0:
- numdigits = -numdigits
- tmp = op1
- else:
- tmp = op2
- tmp.int[0:0] = [0] * numdigits
+
+ if shouldround and numdigits > prec + 1:
+ # Big difference in exponents - check the adjusted exponents
+ tmp_len = len(str(tmp.int))
+ other_len = len(str(other.int))
+ if numdigits > (other_len + prec + 1 - tmp_len):
+ # If the difference in adjusted exps is > prec+1, we know
+ # other is insignificant, so might as well put a 1 after the precision.
+ # (since this is only for addition.) Also stops use of massive longs.
+
+ extend = prec + 2 - tmp_len
+ if extend <= 0:
+ extend = 1
+ tmp.int *= 10 ** extend
+ tmp.exp -= extend
+ other.int = 1
+ other.exp = tmp.exp
+ return op1, op2
+
+ tmp.int *= 10 ** numdigits
+ tmp.exp -= numdigits
return op1, op2
def _adjust_coefficients(op1, op2):
- """Adjust op1, op2 so that op2.int+[0] > op1.int >= op2.int.
+ """Adjust op1, op2 so that op2.int * 10 > op1.int >= op2.int.
Returns the adjusted op1, op2 as well as the change in op1.exp-op2.exp.
Used on _WorkRep instances during division.
"""
adjust = 0
- #If op1 is smaller, get it to same size
- if len(op2.int) > len(op1.int):
- diff = len(op2.int) - len(op1.int)
- op1.int.extend([0]*diff)
- op1.exp -= diff
- adjust = diff
-
- #Same length, wrong order
- if len(op1.int) == len(op2.int) and op1.int < op2.int:
- op1.int.append(0)
+ #If op1 is smaller, make it larger
+ while op2.int > op1.int:
+ op1.int *= 10
op1.exp -= 1
- adjust+= 1
- return op1, op2, adjust
-
- if len(op1.int) > len(op2.int) + 1:
- diff = len(op1.int) - len(op2.int) - 1
- op2.int.extend([0]*diff)
- op2.exp -= diff
- adjust -= diff
+ adjust += 1
- if len(op1.int) == len(op2.int)+1 and op1.int > op2.int:
-
- op2.int.append(0)
+ #If op2 is too small, make it larger
+ while op1.int >= (10 * op2.int):
+ op2.int *= 10
op2.exp -= 1
adjust -= 1
+
return op1, op2, adjust
##### Helper Functions ########################################
+def _convert_other(other):
+ """Convert other to Decimal.
+
+ Verifies that it's ok to use in an implicit construction.
+ """
+ if isinstance(other, Decimal):
+ return other
+ if isinstance(other, (int, long)):
+ return Decimal(other)
+
+ raise TypeError, "You can interact Decimal only with int, long or Decimal data types."
+
_infinity_map = {
'inf' : 1,
'infinity' : 1,