Index: Lib/decimal.py =================================================================== --- Lib/decimal.py (revision 80680) +++ Lib/decimal.py (working copy) @@ -1664,47 +1664,53 @@ exp_min = len(self._int) + self._exp - context.prec if exp_min > Etop: # overflow: exp_min > Etop iff self.adjusted() > Emax + ans = context._raise_error(Overflow, 'above Emax', self._sign) context._raise_error(Inexact) context._raise_error(Rounded) - return context._raise_error(Overflow, 'above Emax', self._sign) + return ans + self_is_subnormal = exp_min < Etiny if self_is_subnormal: - context._raise_error(Subnormal) exp_min = Etiny # round if self has too many digits if self._exp < exp_min: - context._raise_error(Rounded) digits = len(self._int) + self._exp - exp_min if digits < 0: self = _dec_from_triple(self._sign, '1', exp_min-1) digits = 0 - this_function = getattr(self, self._pick_rounding_function[context.rounding]) - changed = this_function(digits) + rounding_method = self._pick_rounding_function[context.rounding] + changed = getattr(self, rounding_method)(digits) coeff = self._int[:digits] or '0' - if changed == 1: + if changed > 0: coeff = str(int(coeff)+1) - ans = _dec_from_triple(self._sign, coeff, exp_min) + if len(coeff) > context.prec: + coeff = coeff[:-1] + exp_min += 1 + # check whether the rounding pushed the exponent out of range + if exp_min > Etop: + ans = context._raise_error(Overflow, 'above Emax', self._sign) + else: + ans = _dec_from_triple(self._sign, coeff, exp_min) + + # raise the appropriate signals, taking care to respect + # the precedence described in the specification + if changed and self_is_subnormal: + context._raise_error(Underflow) + if self_is_subnormal: + context._raise_error(Subnormal) if changed: 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) - 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 = _dec_from_triple(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) + context._raise_error(Rounded) + if not ans: + # raise Clamped on underflow to 0 + context._raise_error(Clamped) return ans + if self_is_subnormal: + context._raise_error(Subnormal) + # fold down if _clamp == 1 and self has too few digits if context._clamp == 1 and self._exp > Etop: context._raise_error(Clamped) Index: Lib/test/test_decimal.py =================================================================== --- Lib/test/test_decimal.py (revision 80681) +++ Lib/test/test_decimal.py (working copy) @@ -43,16 +43,17 @@ Signals = tuple(getcontext().flags.keys()) # Tests are built around these assumed context defaults. +DefaultTestContext = Context( + prec = 9, + rounding = ROUND_HALF_EVEN, + traps = dict.fromkeys(Signals, 0) + ) + # 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) + setcontext(DefaultTestContext.copy()) # decorator for skipping tests on non-IEEE 754 platforms requires_IEEE_754 = unittest.skipUnless( @@ -1723,6 +1724,54 @@ self.assertEqual(repr(context.create_decimal_from_float(10)), "Decimal('10')") +class ExceptionPrecedenceTest(unittest.TestCase): + # In the case that an operation produces multiple signals, and + # more than one of those signals is trapped, the specification + # gives an explicit order in which the signals should be handled: + # + # {DivisionByZero, InvalidOperation, Overflow, Underflow} > + # Subnormal > Inexact > Rounded > Clamped + # + # The first four signals above should never coincide, so their + # ordering with respect to one another doesnt matter. + OrderedSignals = (Clamped, Rounded, Inexact, Subnormal, + Underflow, Overflow, DivisionByZero, InvalidOperation) + + def check_precedence(self, op, *args): + # check exception precedence for a single operation + + with localcontext(DefaultTestContext) as ctx: + # turn all traps off + for exception in Signals: + ctx.traps[exception] = False + + # find the signals raised by this operation + ctx.clear_flags() + result = op(*args) + op_signals = [e for e in self.OrderedSignals if ctx.flags[e]] + + # now turn traps on one-by-one, from the lowest signal in + # the ordering to the highest, and check that we get the + # appropriate exception each time + for exception in op_signals: + ctx.traps[exception] = True + try: + result = op(*args) + except exception: + pass + except Signals as e: + self.fail("%s(*%s) in context %s raised %s; expected %s" % + (op, args, ctx, type(e), exception)) + else: + self.fail("%s(*%s) in context %s did not raise; expected %s" % + (op, args, ctx, exception)) + + def test_precedence(self): + self.check_precedence(Decimal.exp, Decimal(1)) + self.check_precedence(Decimal.exp, Decimal('1e100')) + self.check_precedence(Decimal.exp, Decimal('-1e100')) + + class ContextAPItests(unittest.TestCase): def test_pickle(self): @@ -2274,6 +2323,7 @@ DecimalUseOfContextTest, DecimalUsabilityTest, DecimalPythonAPItests, + ExceptionPrecedenceTest, ContextAPItests, DecimalTest, WithStatementTest,