Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ece0697

Browse files
committed
Merged revisions 80755 via svnmerge from
svn+ssh://[email protected]/python/branches/py3k ................ r80755 | mark.dickinson | 2010-05-04 15:35:33 +0100 (Tue, 04 May 2010) | 17 lines Merged revisions 80753 via svnmerge from svn+ssh://[email protected]/python/trunk ........ r80753 | mark.dickinson | 2010-05-04 15:25:50 +0100 (Tue, 04 May 2010) | 10 lines Issue #8567: Fix incorrect precedence of signals in Decimal module. When a Decimal operation raises multiple signals and more than one of those signals is trapped, the specification determines the order in which the signals should be handled. In many cases this order wasn't being followed, leading to the wrong Python exception being raised. This commit fixes those cases, and adds extra tests. The tests are only enabled when EXTENDEDERRORTESTS is True, since they involve rerunning each Decimal testcase several times. ........ ................
1 parent a2d5b34 commit ece0697

3 files changed

Lines changed: 114 additions & 43 deletions

File tree

Lib/decimal.py

Lines changed: 83 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1626,47 +1626,53 @@ def _fix(self, context):
16261626
exp_min = len(self._int) + self._exp - context.prec
16271627
if exp_min > Etop:
16281628
# overflow: exp_min > Etop iff self.adjusted() > Emax
1629+
ans = context._raise_error(Overflow, 'above Emax', self._sign)
16291630
context._raise_error(Inexact)
16301631
context._raise_error(Rounded)
1631-
return context._raise_error(Overflow, 'above Emax', self._sign)
1632+
return ans
1633+
16321634
self_is_subnormal = exp_min < Etiny
16331635
if self_is_subnormal:
1634-
context._raise_error(Subnormal)
16351636
exp_min = Etiny
16361637

16371638
# round if self has too many digits
16381639
if self._exp < exp_min:
1639-
context._raise_error(Rounded)
16401640
digits = len(self._int) + self._exp - exp_min
16411641
if digits < 0:
16421642
self = _dec_from_triple(self._sign, '1', exp_min-1)
16431643
digits = 0
1644-
this_function = getattr(self, self._pick_rounding_function[context.rounding])
1645-
changed = this_function(digits)
1644+
rounding_method = self._pick_rounding_function[context.rounding]
1645+
changed = getattr(self, rounding_method)(digits)
16461646
coeff = self._int[:digits] or '0'
1647-
if changed == 1:
1647+
if changed > 0:
16481648
coeff = str(int(coeff)+1)
1649-
ans = _dec_from_triple(self._sign, coeff, exp_min)
1649+
if len(coeff) > context.prec:
1650+
coeff = coeff[:-1]
1651+
exp_min += 1
16501652

1653+
# check whether the rounding pushed the exponent out of range
1654+
if exp_min > Etop:
1655+
ans = context._raise_error(Overflow, 'above Emax', self._sign)
1656+
else:
1657+
ans = _dec_from_triple(self._sign, coeff, exp_min)
1658+
1659+
# raise the appropriate signals, taking care to respect
1660+
# the precedence described in the specification
1661+
if changed and self_is_subnormal:
1662+
context._raise_error(Underflow)
1663+
if self_is_subnormal:
1664+
context._raise_error(Subnormal)
16511665
if changed:
16521666
context._raise_error(Inexact)
1653-
if self_is_subnormal:
1654-
context._raise_error(Underflow)
1655-
if not ans:
1656-
# raise Clamped on underflow to 0
1657-
context._raise_error(Clamped)
1658-
elif len(ans._int) == context.prec+1:
1659-
# we get here only if rescaling rounds the
1660-
# cofficient up to exactly 10**context.prec
1661-
if ans._exp < Etop:
1662-
ans = _dec_from_triple(ans._sign,
1663-
ans._int[:-1], ans._exp+1)
1664-
else:
1665-
# Inexact and Rounded have already been raised
1666-
ans = context._raise_error(Overflow, 'above Emax',
1667-
self._sign)
1667+
context._raise_error(Rounded)
1668+
if not ans:
1669+
# raise Clamped on underflow to 0
1670+
context._raise_error(Clamped)
16681671
return ans
16691672

1673+
if self_is_subnormal:
1674+
context._raise_error(Subnormal)
1675+
16701676
# fold down if _clamp == 1 and self has too few digits
16711677
if context._clamp == 1 and self._exp > Etop:
16721678
context._raise_error(Clamped)
@@ -2293,6 +2299,7 @@ def __pow__(self, other, modulo=None, context=None):
22932299
# from here on, the result always goes through the call
22942300
# to _fix at the end of this function.
22952301
ans = None
2302+
exact = False
22962303

22972304
# crude test to catch cases of extreme overflow/underflow. If
22982305
# log10(self)*other >= 10**bound and bound >= len(str(Emax))
@@ -2317,6 +2324,7 @@ def __pow__(self, other, modulo=None, context=None):
23172324
ans = self._power_exact(other, context.prec + 1)
23182325
if ans is not None and result_sign == 1:
23192326
ans = _dec_from_triple(1, ans._int, ans._exp)
2327+
exact = True
23202328

23212329
# usual case: inexact result, x**y computed directly as exp(y*log(x))
23222330
if ans is None:
@@ -2339,24 +2347,55 @@ def __pow__(self, other, modulo=None, context=None):
23392347

23402348
ans = _dec_from_triple(result_sign, str(coeff), exp)
23412349

2342-
# the specification says that for non-integer other we need to
2343-
# raise Inexact, even when the result is actually exact. In
2344-
# the same way, we need to raise Underflow here if the result
2345-
# is subnormal. (The call to _fix will take care of raising
2346-
# Rounded and Subnormal, as usual.)
2347-
if not other._isinteger():
2348-
context._raise_error(Inexact)
2349-
# pad with zeros up to length context.prec+1 if necessary
2350+
# unlike exp, ln and log10, the power function respects the
2351+
# rounding mode; no need to switch to ROUND_HALF_EVEN here
2352+
2353+
# There's a difficulty here when 'other' is not an integer and
2354+
# the result is exact. In this case, the specification
2355+
# requires that the Inexact flag be raised (in spite of
2356+
# exactness), but since the result is exact _fix won't do this
2357+
# for us. (Correspondingly, the Underflow signal should also
2358+
# be raised for subnormal results.) We can't directly raise
2359+
# these signals either before or after calling _fix, since
2360+
# that would violate the precedence for signals. So we wrap
2361+
# the ._fix call in a temporary context, and reraise
2362+
# afterwards.
2363+
if exact and not other._isinteger():
2364+
# pad with zeros up to length context.prec+1 if necessary; this
2365+
# ensures that the Rounded signal will be raised.
23502366
if len(ans._int) <= context.prec:
2351-
expdiff = context.prec+1 - len(ans._int)
2367+
expdiff = context.prec + 1 - len(ans._int)
23522368
ans = _dec_from_triple(ans._sign, ans._int+'0'*expdiff,
23532369
ans._exp-expdiff)
2354-
if ans.adjusted() < context.Emin:
2355-
context._raise_error(Underflow)
23562370

2357-
# unlike exp, ln and log10, the power function respects the
2358-
# rounding mode; no need to use ROUND_HALF_EVEN here
2359-
ans = ans._fix(context)
2371+
# create a copy of the current context, with cleared flags/traps
2372+
newcontext = context.copy()
2373+
newcontext.clear_flags()
2374+
for exception in _signals:
2375+
newcontext.traps[exception] = 0
2376+
2377+
# round in the new context
2378+
ans = ans._fix(newcontext)
2379+
2380+
# raise Inexact, and if necessary, Underflow
2381+
newcontext._raise_error(Inexact)
2382+
if newcontext.flags[Subnormal]:
2383+
newcontext._raise_error(Underflow)
2384+
2385+
# propagate signals to the original context; _fix could
2386+
# have raised any of Overflow, Underflow, Subnormal,
2387+
# Inexact, Rounded, Clamped. Overflow needs the correct
2388+
# arguments. Note that the order of the exceptions is
2389+
# important here.
2390+
if newcontext.flags[Overflow]:
2391+
context._raise_error(Overflow, 'above Emax', ans._sign)
2392+
for exception in Underflow, Subnormal, Inexact, Rounded, Clamped:
2393+
if newcontext.flags[exception]:
2394+
context._raise_error(exception)
2395+
2396+
else:
2397+
ans = ans._fix(context)
2398+
23602399
return ans
23612400

23622401
def __rpow__(self, other, context=None):
@@ -2450,14 +2489,15 @@ def quantize(self, exp, rounding=None, context=None, watchexp=True):
24502489
'quantize result has too many digits for current context')
24512490

24522491
# raise appropriate flags
2492+
if ans and ans.adjusted() < context.Emin:
2493+
context._raise_error(Subnormal)
24532494
if ans._exp > self._exp:
2454-
context._raise_error(Rounded)
24552495
if ans != self:
24562496
context._raise_error(Inexact)
2457-
if ans and ans.adjusted() < context.Emin:
2458-
context._raise_error(Subnormal)
2497+
context._raise_error(Rounded)
24592498

2460-
# call to fix takes care of any necessary folddown
2499+
# call to fix takes care of any necessary folddown, and
2500+
# signals Clamped if necessary
24612501
ans = ans._fix(context)
24622502
return ans
24632503

@@ -2556,10 +2596,10 @@ def to_integral_exact(self, rounding=None, context=None):
25562596
context = getcontext()
25572597
if rounding is None:
25582598
rounding = context.rounding
2559-
context._raise_error(Rounded)
25602599
ans = self._rescale(0, rounding)
25612600
if ans != self:
25622601
context._raise_error(Inexact)
2602+
context._raise_error(Rounded)
25632603
return ans
25642604

25652605
def to_integral_value(self, rounding=None, context=None):
@@ -3439,13 +3479,13 @@ def next_toward(self, other, context=None):
34393479
context._raise_error(Overflow,
34403480
'Infinite result from next_toward',
34413481
ans._sign)
3442-
context._raise_error(Rounded)
34433482
context._raise_error(Inexact)
3483+
context._raise_error(Rounded)
34443484
elif ans.adjusted() < context.Emin:
34453485
context._raise_error(Underflow)
34463486
context._raise_error(Subnormal)
3447-
context._raise_error(Rounded)
34483487
context._raise_error(Inexact)
3488+
context._raise_error(Rounded)
34493489
# if precision == 1 then we don't raise Clamped for a
34503490
# result 0E-Etiny.
34513491
if not ans:

Lib/test/test_decimal.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@
4141
# Useful Test Constant
4242
Signals = tuple(getcontext().flags.keys())
4343

44+
# Signals ordered with respect to precedence: when an operation
45+
# produces multiple signals, signals occurring later in the list
46+
# should be handled before those occurring earlier in the list.
47+
OrderedSignals = (Clamped, Rounded, Inexact, Subnormal,
48+
Underflow, Overflow, DivisionByZero, InvalidOperation)
49+
4450
# Tests are built around these assumed context defaults.
4551
# test_main() restores the original context.
4652
def init():
@@ -346,6 +352,25 @@ def FixQuotes(val):
346352
else:
347353
self.fail("Did not raise %s in %s" % (error, s))
348354
self.context.traps[error] = 0
355+
356+
# as above, but add traps cumulatively, to check precedence
357+
ordered_errors = [e for e in OrderedSignals if e in theirexceptions]
358+
for error in ordered_errors:
359+
self.context.traps[error] = 1
360+
try:
361+
funct(*vals)
362+
except error:
363+
pass
364+
except Signals as e:
365+
self.fail("Raised %s in %s; expected %s" %
366+
(type(e), s, error))
367+
else:
368+
self.fail("Did not raise %s in %s" % (error, s))
369+
# reset traps
370+
for error in ordered_errors:
371+
self.context.traps[error] = 0
372+
373+
349374
if DEBUG:
350375
print("--", self.context)
351376
try:

Misc/NEWS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ Core and Builtins
4040
Library
4141
-------
4242

43+
- Issue #8567: Fix precedence of signals in Decimal module: when a
44+
Decimal operation raises multiple signals and more than one of those
45+
signals is trapped, the specification determines the order in which
46+
the signals should be handled. In many cases this order wasn't
47+
being followed, leading to the wrong Python exception being raised.
48+
4349
- Issue #7865: The close() method of :mod:`io` objects should not swallow
4450
exceptions raised by the implicit flush(). Also ensure that calling
4551
close() several times is supported. Patch by Pascal Chambon.

0 commit comments

Comments
 (0)