Skip to content

_pydecimal loses companion signal flags when a signal is trapped #160945

Open

Description

@arpittkhandelwal

Bug report

Bug description

The pure-Python decimal implementation (_pydecimal) does not set the
context flags of companion conditions when an operation's result is
delivered through a trapped signal. The C implementation (_decimal,
libmpdec) records the full set of conditions in the context flags before
raising the trapped exception.

Minimal example — an overflowing multiplication signals Overflow,
Inexact and Rounded; Overflow is trapped (as it is in the default
context):

import _decimal as C
import _pydecimal as P

def probe(module):
    ctx = module.Context(prec=9, Emin=-999, Emax=999)
    ctx.clear_flags()
    try:
        ctx.multiply(module.Decimal('1E+800'), module.Decimal('1E+800'))
    except module.Overflow:
        pass
    return sorted(s.__name__ for s, v in ctx.flags.items() if v)

print("C _decimal:  ", probe(C))
print("P _pydecimal:", probe(P))

Output (verified on CPython 3.12 and on a 3.16 build):

C _decimal:   ['Inexact', 'Overflow', 'Rounded']
P _pydecimal: ['Overflow']          # Inexact and Rounded are lost

The same loss occurs for every operation that signals several conditions
at once whenever any of them is trapped, e.g.:

Case (single trap) _decimal flags after the exception _pydecimal flags
divide(1, 3), trap Inexact Inexact, Rounded Inexact
divide(1E-999, 1E+10), trap Underflow Clamped, Inexact, Rounded, Subnormal, Underflow Underflow
divide(1E-999, 1E+10), trap Subnormal Clamped, Inexact, Rounded, Subnormal, Underflow Subnormal, Underflow
divmod(5, 0), trap DivisionByZero DivisionByZero, InvalidOperation DivisionByZero
quantize(1.23, 1E+995) with clamp=1, trap Inexact Clamped, Inexact, Rounded Inexact

With no traps enabled the two implementations agree exactly.

Expected vs actual

Per the documentation (Doc/library/decimal.rst, "Signals"): "For each
signal there is a flag and a trap enabler. When a signal is encountered,
its flag is set to one, then, if the trap enabler is set to one, an
exception is raised."
An overflowing operation encounters Inexact
and Rounded — both implementations set those flags whenever Overflow
is not trapped — so the flags should be set regardless of whether some
other signal is trapped. The two implementations are interchangeable
backends of the same module, so code that catches the exception and then
inspects context.flags (a documented monitoring pattern) must observe
the same state with either backend.

Root cause: in Lib/_pydecimal.py, Context._raise_error() sets one
signal's flag and raises immediately when that signal is trapped.
Operations signal companion conditions through sequential _raise_error
calls (e.g. Decimal._fix signals Overflow, then Inexact, then
Rounded), so a trapped signal makes the remaining calls unreachable.

Note that which exception is raised is correct in both implementations
(the precedence order established by bpo-8567); only the flag state left
behind differs. This is also why the existing test suite does not catch
it: the decTest driver disables the traps of the expected conditions for
its flag comparisons, and the extended error tests assert only the
exception type, never the resulting flags.

CPython versions tested on:

3.12, 3.16

Operating systems tested on:

No response

Linked PRs

Activity

  1. added a commit that references this issue on Oct 30, 2026
  2. added
    stdlibStandard Library Python modules in the Lib/ directory
    on Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions