[Python-checkins] r57934 - python/branches/decimal-branch/Lib/decimal.py
facundo.batista
python-checkins at python.org
Mon Sep 3 22:53:55 CEST 2007
Author: facundo.batista
Date: Mon Sep 3 22:53:54 2007
New Revision: 57934
Modified:
python/branches/decimal-branch/Lib/decimal.py
Log:
Several small changes from Mike Dickinson, that in general make the
code simpler and more readeable (and in some cases more
'mathematically correct'.
_rescale is much shorter and simpler; it's now a quiet function that
doesn't raise any flags and doesn't use any information from the context.
_increment and all the rounding mode functions (_round_*) have had their
context arguments removed; the rounding mode functions no longer need the
expdiff argument either.
_fixexponents has been absorbed into _fix.
Decimals created via Decimal() never have extra leading zeros.
Modified: python/branches/decimal-branch/Lib/decimal.py
==============================================================================
--- python/branches/decimal-branch/Lib/decimal.py (original)
+++ python/branches/decimal-branch/Lib/decimal.py Mon Sep 3 22:53:54 2007
@@ -998,13 +998,13 @@
return ans
if not self:
exp = max(exp, other._exp - context.prec-1)
- ans = other._rescale(exp, watchexp=0, context=context)
+ ans = other._rescale(exp, context.rounding)
if shouldround:
ans = ans._fix(context)
return ans
if not other:
exp = max(exp, self._exp - context.prec-1)
- ans = self._rescale(exp, watchexp=0, context=context)
+ ans = self._rescale(exp, context.rounding)
if shouldround:
ans = ans._fix(context)
return ans
@@ -1079,23 +1079,17 @@
tmp._sign = 1 - tmp._sign
return other.__add__(tmp, context=context)
- def _increment(self, round=1, context=None):
+ def _increment(self):
"""Special case of add, adding 1eExponent
Since it is common, (rounding, for example) this adds
(sign)*one E self._exp to the number more efficiently than add.
+ Assumes that self is nonspecial.
+
For example:
Decimal('5.624e10')._increment() == Decimal('5.625e10')
"""
- if self._is_special:
- ans = self._check_nans(context=context)
- if ans:
- return ans
-
- # Must be infinite, and incrementing makes no difference
- return Decimal(self)
-
L = list(self._int)
L[-1] += 1
spot = len(L)-1
@@ -1106,13 +1100,7 @@
break
L[spot-1] += 1
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)
- return ans
+ return Decimal((self._sign, L, self._exp))
def __mul__(self, other, context=None):
"""Return self * other.
@@ -1273,7 +1261,7 @@
if divmod == 1 or divmod == 3:
exp = min(self._exp, other._exp)
- ans2 = self._rescale(exp, context=context, watchexp=0)
+ ans2 = self._rescale(exp, context.rounding)
if shouldround:
ans2 = ans2._fix(context)
return (Decimal( (sign, (0,), 0) ),
@@ -1299,11 +1287,8 @@
if res.int >= prec_limit and shouldround:
return context._raise_error(DivisionImpossible)
otherside = Decimal(op1)
- frozen = context._ignore_all_flags()
-
exp = min(self._exp, other._exp)
- otherside = otherside._rescale(exp, context=context, watchexp=0)
- context._regard_flags(*frozen)
+ otherside = otherside._rescale(exp, context.rounding)
if shouldround and (divmod == 1 or divmod == 3):
otherside = otherside._fix(context)
return (Decimal(res), otherside)
@@ -1522,251 +1507,140 @@
self - Decimal instance
context - context used.
"""
+
+ if context is None:
+ context = getcontext()
+
if self._is_special:
if self._isnan():
+ # decapitate payload if necessary
return self._fix_nan(context)
else:
+ # self is +/-Infinity; return unaltered
return self
- if context is None:
- context = getcontext()
- prec = context.prec
- ans = self._fixexponents(context)
- if len(ans._int) > prec:
- ans = ans._round(prec, context=context)
- ans = ans._fixexponents(context)
- return ans
- def _fixexponents(self, context):
- """Fix the exponents and return a copy with the exponent in bounds.
- Only call if known to not be a special value.
- """
-
- ans = self
- # deal with zeros first
- if not ans:
- Etiny = context.Etiny()
- if ans._exp < Etiny:
- ans = Decimal(self)
- ans._exp = Etiny
+ # if self is zero then exponent should be between Etiny and
+ # Emax if _clamp==0, and between Etiny and Etop if _clamp==1.
+ Etiny = context.Etiny()
+ Etop = context.Etop()
+ if not self:
+ exp_max = [context.Emax, Etop][context._clamp]
+ new_exp = min(max(self._exp, Etiny), exp_max)
+ if new_exp != self._exp:
context._raise_error(Clamped)
+ return Decimal((self._sign, (0,), new_exp))
else:
- if context._clamp:
- exp_max = context.Etop()
- else:
- exp_max = context.Emax
- if ans._exp > exp_max:
- ans = Decimal(self)
- ans._exp = exp_max
- context._raise_error(Clamped)
- return ans
+ return self
- # self is nonzero; if adjusted exponent is > Emax, overflow
- ans_adjusted = ans.adjusted()
- if ans_adjusted > context.Emax:
+ # exp_min is the smallest allowable exponent of the result,
+ # equal to max(self.adjusted()-context.prec+1, Etiny)
+ exp_min = len(self._int) + self._exp - context.prec
+ if exp_min > Etop:
+ # overflow: exp_min > Etop iff self.adjusted() > Emax
context._raise_error(Inexact)
context._raise_error(Rounded)
- c = context._raise_error(Overflow, 'above Emax', ans._sign)
- return c
-
- # Now check for subnormal results, and for the need to fold
- # down. These two conditions are *not* mutually
- # exclusive---when the precision is large and Emin and Emax
- # are small it's quite possible to have Emin > Etop.
- # (Actually, the specification requires Emin and Emax to be at
- # least 5*precision, so this shouldn't happen, but it never
- # hurts to be careful.)
- if context._clamp:
- Etop = context.Etop()
- if ans._exp > Etop:
- context._raise_error(Clamped)
- ans = ans._rescale(Etop, context=context)
-
- if ans_adjusted < context.Emin:
+ return context._raise_error(Overflow, 'above Emax', self._sign)
+ self_is_subnormal = exp_min < Etiny
+ if self_is_subnormal:
context._raise_error(Subnormal)
- Etiny = context.Etiny()
- if ans._exp < Etiny:
- ans_before_rescale = ans
- ans = ans._rescale(Etiny, context=context)
- if ans != ans_before_rescale:
+ exp_min = Etiny
+
+ # round if self has too many digits
+ if self._exp < exp_min:
+ context._raise_error(Rounded)
+ ans = self._rescale(exp_min, context.rounding)
+ if ans != self:
+ context._raise_error(Inexact)
+ if self_is_subnormal:
context._raise_error(Underflow)
if not ans:
+ # raise Clamped on underflow to 0
context._raise_error(Clamped)
-
- return ans
-
- def _round(self, prec=None, rounding=None, context=None, forceExp=None, fromQuantize=False):
- """Returns a rounded version of self.
-
- You can specify the precision or rounding method. Otherwise, the
- 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()
-
- if rounding is None:
- rounding = context.rounding
- if prec is None:
- prec = context.prec
-
- if not self:
- if prec <= 0:
- dig = (0,)
- exp = len(self._int) - prec + self._exp
- else:
- dig = (0,) * prec
- exp = len(self._int) + self._exp - prec
- ans = Decimal((self._sign, dig, exp))
- context._raise_error(Rounded)
- return ans
-
- if prec == 0:
- temp = Decimal(self)
- temp._int = (0,)+temp._int
- prec = 1
- elif prec < 0:
- exp = self._exp + len(self._int) - prec - 1
- temp = Decimal( (self._sign, (0, 1), exp))
- prec = 1
- else:
- temp = Decimal(self)
-
- # See if we need to extend precision
- expdiff = prec - len(temp._int)
-
- # not allowing subnormal for quantize
- if fromQuantize and (forceExp - context.Emax) > context.prec:
- return context._raise_error(InvalidOperation, "Quantize doesn't allow subnormal")
-
- if expdiff >= 0:
- if fromQuantize and len(temp._int)+expdiff > context.prec:
- return context._raise_error(InvalidOperation, 'Beyond guarded precision')
- tmp = list(temp._int)
- tmp.extend([0] * expdiff)
- ans = Decimal( (temp._sign, tmp, temp._exp - expdiff))
- return ans
-
- # OK, but maybe all the lost digits are 0.
- lostdigits = self._int[expdiff:]
- if lostdigits == (0,) * len(lostdigits):
- ans = Decimal( (temp._sign, temp._int[:prec], temp._exp - expdiff))
- # Rounded, but not Inexact
- context._raise_error(Rounded)
+ elif len(ans._int) == context.prec+1:
+ # we get here only if rescaling rounds the
+ # cofficient up to exactly 10**context.prec
+ if ans._exp < Etop:
+ ans = Decimal((ans._sign, ans._int[:-1], ans._exp+1))
+ else:
+ # Inexact and Rounded have already been raised
+ ans = context._raise_error(Overflow, 'above Emax',
+ self._sign)
return ans
- # Okay, let's round and lose data, let's get the correct rounding function
- this_function = getattr(temp, self._pick_rounding_function[rounding])
-
- # Now we've got the rounding function
- ans = this_function(prec, expdiff, context)
-
- if forceExp is not None:
- exp = forceExp
- if fromQuantize and not (context.Emin <= exp <= context.Emax):
- if (context.Emin <= ans._exp) and ans._int == (0,):
- return context._raise_error(InvalidOperation)
-
- newdiff = ans._exp - exp
- if newdiff >= 0:
- ans._int = ans._int + tuple([0]*newdiff)
- else:
- ans._int = (0,)
- ans._exp = exp
+ # fold down if _clamp == 1 and self has too few digits
+ if context._clamp == 1 and self._exp > Etop:
+ context._raise_error(Clamped)
+ self_padded = self._int + (0,)*(self._exp - Etop)
+ return Decimal((self._sign, self_padded, Etop))
- if context.Emin < exp < context.Emax:
- if len(ans._int) > context.prec:
- return context._raise_error(InvalidOperation, 'Beyond guarded precision')
-
- context._raise_error(Rounded)
- context._raise_error(Inexact, 'Changed in rounding')
- return ans
+ # here self was representable to begin with; return unchanged
+ return self
_pick_rounding_function = {}
- def _round_down(self, prec, expdiff, context):
- """Also known as round-towards-0, truncate."""
- return Decimal( (self._sign, self._int[:prec], self._exp - expdiff) )
+ # for each of the rounding functions below:
+ # self is a finite, nonzero Decimal
+ # prec is an integer satisfying 0 <= prec < len(self._int)
+ # the rounded result will have exponent self._exp + len(self._int) - prec;
- def _round_half_up(self, prec, expdiff, context, tmp = None):
- """Rounds 5 up (away from 0)"""
+ def _round_down(self, prec):
+ """Also known as round-towards-0, truncate."""
+ newexp = self._exp + len(self._int) - prec
+ return Decimal((self._sign, self._int[:prec] or (0,), newexp))
- if tmp is None:
- tmp = Decimal( (self._sign,self._int[:prec], self._exp - expdiff))
- if self._int[prec] >= 5:
- tmp = tmp._increment(round=0, context=context)
- if len(tmp._int) > prec:
- return Decimal( (tmp._sign, tmp._int[:-1], tmp._exp + 1))
+ def _round_up(self, prec):
+ """Rounds away from 0."""
+ newexp = self._exp + len(self._int) - prec
+ tmp = Decimal((self._sign, self._int[:prec] or (0,), newexp))
+ for digit in self._int[prec:]:
+ if digit != 0:
+ return tmp._increment()
return tmp
- def _round_half_even(self, prec, expdiff, context):
- """Round 5 to even, rest to nearest."""
-
- tmp = Decimal( (self._sign, self._int[:prec], self._exp - expdiff))
- half = (self._int[prec] == 5)
- if half:
- for digit in self._int[prec+1:]:
- if digit != 0:
- half = 0
- break
- if half:
- if self._int[prec-1] & 1 == 0:
- return tmp
- return self._round_half_up(prec, expdiff, context, tmp)
+ def _round_half_up(self, prec):
+ """Rounds 5 up (away from 0)"""
+ if self._int[prec] >= 5:
+ return self._round_up(prec)
+ else:
+ return self._round_down(prec)
- def _round_half_down(self, prec, expdiff, context):
+ def _round_half_down(self, prec):
"""Round 5 down"""
-
- tmp = Decimal( (self._sign, self._int[:prec], self._exp - expdiff))
- half = (self._int[prec] == 5)
- if half:
+ if self._int[prec] == 5:
for digit in self._int[prec+1:]:
if digit != 0:
- half = 0
break
- if half:
- return tmp
- return self._round_half_up(prec, expdiff, context, tmp)
+ else:
+ return self._round_down(prec)
+ return self._round_half_up(prec)
- def _round_up(self, prec, expdiff, context):
- """Rounds away from 0."""
- tmp = Decimal( (self._sign, self._int[:prec], self._exp - expdiff) )
- for digit in self._int[prec:]:
- if digit != 0:
- tmp = tmp._increment(round=0, context=context)
- if len(tmp._int) > prec:
- return Decimal( (tmp._sign, tmp._int[:-1], tmp._exp + 1))
- else:
- return tmp
- return tmp
+ def _round_half_even(self, prec):
+ """Round 5 to even, rest to nearest."""
+ if prec and self._int[prec-1] & 1:
+ return self._round_half_up(prec)
+ else:
+ return self._round_half_down(prec)
- def _round_ceiling(self, prec, expdiff, context):
+ def _round_ceiling(self, prec):
"""Rounds up (not away from 0 if negative.)"""
if self._sign:
- return self._round_down(prec, expdiff, context)
+ return self._round_down(prec)
else:
- return self._round_up(prec, expdiff, context)
+ return self._round_up(prec)
- def _round_floor(self, prec, expdiff, context):
+ def _round_floor(self, prec):
"""Rounds down (not towards 0 if negative)"""
if not self._sign:
- return self._round_down(prec, expdiff, context)
+ return self._round_down(prec)
else:
- return self._round_up(prec, expdiff, context)
+ return self._round_up(prec)
- def _round_05up(self, prec, expdiff, context):
+ def _round_05up(self, prec):
"""Round down unless digit prec-1 is 0 or 5."""
- if self._int[prec-1] in (0, 5):
- return self._round_up(prec, expdiff, context)
+ if prec == 0 or self._int[prec-1] in (0, 5):
+ return self._round_up(prec)
else:
- return self._round_down(prec, expdiff, context)
+ return self._round_down(prec)
def fma(self, other, third, context=None):
"""Fused multiply-add.
@@ -2336,13 +2210,16 @@
return Decimal( (dup._sign, dup._int[:end], exp) )
- def quantize(self, exp, rounding=None, context=None, watchexp=1):
+ def quantize(self, exp, rounding=None, context=None):
"""Quantize self so its exponent is the same as that of exp.
Similar to self._rescale(exp._exp) but with error checking.
"""
if context is None:
context = getcontext()
+ if rounding is None:
+ rounding = context.rounding
+
if self._is_special or exp._is_special:
ans = self._check_nans(exp, context)
if ans:
@@ -2353,7 +2230,40 @@
return self # if both are inf, it is OK
return context._raise_error(InvalidOperation,
'quantize with one INF')
- ans = self._rescale(exp._exp, rounding, context, watchexp=0, fromQuantize=True)
+
+ # exp._exp should be between Etiny and Emax
+ if not (context.Etiny() <= exp._exp <= context.Emax):
+ return context._raise_error(InvalidOperation,
+ 'target exponent out of bounds in quantize')
+
+ if not self:
+ ans = Decimal((self._sign, (0,), exp._exp))
+ return ans._fix(context)
+
+ self_adjusted = self.adjusted()
+ if self_adjusted > context.Emax:
+ return context._raise_error(InvalidOperation,
+ 'exponent of quantize result too large for current context')
+ if self_adjusted - exp._exp + 1 > context.prec:
+ return context._raise_error(InvalidOperation,
+ 'quantize result has too many digits for current context')
+
+ ans = self._rescale(exp._exp, rounding)
+ if ans.adjusted() > context.Emax:
+ return context._raise_error(InvalidOperation,
+ 'exponent of quantize result too large for current context')
+ if len(ans._int) > context.prec:
+ return context._raise_error(InvalidOperation,
+ 'quantize result has too many digits for current context')
+
+ # raise appropriate flags
+ if ans._exp > self._exp:
+ context._raise_error(Rounded)
+ if ans != self:
+ context._raise_error(Inexact)
+ if ans and ans.adjusted() < context.Emin:
+ context._raise_error(Subnormal)
+
# call to fix takes care of any necessary folddown
ans = ans._fix(context)
return ans
@@ -2370,58 +2280,34 @@
return self._isinfinity() and other._isinfinity() and True
return self._exp == other._exp
- def _rescale(self, exp, rounding=None, context=None, watchexp=1, fromQuantize=False):
- """Rescales so that the exponent is exp.
+ def _rescale(self, exp, rounding):
+ """Rescale self so that the exponent is exp, either by padding with zeros
+ or by truncating digits, using the given rounding mode.
+
+ Specials are returned without change. This operation is
+ quiet: it raises no flags, and uses no information from the
+ context.
exp = exp to scale to (an integer)
- rounding = rounding version
- watchexp: if set (default) an error is returned if exp is greater
- than Emax or less than Etiny.
+ rounding = rounding mode
"""
- if context is None:
- context = getcontext()
-
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
-
- if fromQuantize and (context.Emax < exp or context.Etiny() > exp):
- return context._raise_error(InvalidOperation, 'rescale(a, INF)')
- if fromQuantize and exp < context.Etiny():
- return context._raise_error(InvalidOperation, '"rhs" must be no less than Etiny')
-
+ return self
if not self:
- ans = Decimal(self)
- ans._int = (0,)
- ans._exp = exp
- return ans
-
- diff = self._exp - exp
- digits = len(self._int) + diff
-
- if watchexp and digits > context.prec:
- return context._raise_error(InvalidOperation, 'Rescale > prec')
-
- tmp = Decimal(self)
+ return Decimal((self._sign, (0,), exp))
+ if self._exp >= exp:
+ # pad answer with zeros if necessary
+ return Decimal((self._sign, self._int + (0,)*(self._exp - exp), exp))
+
+ # too many digits; round and lose data. If self.adjusted() <
+ # exp-1, replace self by 10**(exp-1) before rounding
+ digits = len(self._int) + self._exp - exp
if digits < 0:
- tmp._exp = exp - 1
- tmp._int = (1,)
+ self = Decimal((self._sign, (1,), exp-1))
digits = 0
- tmp = tmp._round(digits, rounding, context=context, forceExp=exp,
- fromQuantize=fromQuantize)
-
- if watchexp or fromQuantize:
- tmp_adjusted = tmp.adjusted()
- if tmp and tmp_adjusted < context.Emin:
- context._raise_error(Subnormal)
- elif tmp and tmp_adjusted > context.Emax:
- return context._raise_error(InvalidOperation, 'rescale(a, INF)')
- return tmp
+ this_function = getattr(self, self._pick_rounding_function[rounding])
+ return this_function(digits)
def to_integral_exact(self, rounding=None, context=None):
"""Rounds to a nearby integer.
@@ -2437,26 +2323,36 @@
ans = self._check_nans(context=context)
if ans:
return ans
+ return self
if self._exp >= 0:
return self
+ if not self:
+ return Decimal((self._sign, (0,), 0))
if context is None:
context = getcontext()
- return self._rescale(0, rounding, context=context)
+ if rounding is None:
+ rounding = context.rounding
+ context._raise_error(Rounded)
+ ans = self._rescale(0, rounding)
+ if ans != self:
+ context._raise_error(Inexact)
+ return ans
def to_integral_value(self, rounding=None, context=None):
"""Rounds to the nearest integer, without raising inexact, rounded."""
+ if context is None:
+ context = getcontext()
+ if rounding is None:
+ rounding = context.rounding
if self._is_special:
ans = self._check_nans(context=context)
if ans:
return ans
+ return self
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)
- return ans
+ else:
+ return self._rescale(0, rounding)
# the method name changed, but we provide also the old one, for compatibility
to_integral = to_integral_value
More information about the Python-checkins
mailing list