Skip to main content

Modules/_decimal/_decimal.c

Source:

cpython 3.14 @ ab2d84fe1023/Modules/_decimal/_decimal.c

The _decimal extension module wraps the mpdecimal library to implement IEEE 754-2008 decimal floating-point arithmetic. Every Python-level Decimal object corresponds to an mpd_t value stored inside a PyDecObject, and every Context object wraps an mpd_context_t that controls rounding, precision, and trap flags.

Map

SymbolKindLines (approx)Purpose
PyDecObjectstruct120-145Holds mpd_t value plus reference to owning context
PyDecContextObjectstruct160-185Holds mpd_context_t plus thread-local flag
PyDec_TypePyTypeObject6800-6950Top-level Decimal type registration
PyDecContext_TypePyTypeObject6950-7000Context type registration
decimal_newfunction520-720Decimal.__new__ dispatcher
PyDec_Add / PyDec_Mul / PyDec_Divfunctions1800-2100Arithmetic via mpdecimal traps
DecimalException hierarchytype objects280-510Maps mpd_err_t codes to Python exceptions
context_getprec / context_setprecgetset4100-4250Context precision property
context_getround / context_setroundgetset4260-4350Context rounding mode property
PyDec_Context_Get / PyDec_Context_Setfunctions3900-4050Thread-local context access

Reading

PyDecObject and PyDec_Type

PyDecObject is a standard Python heap type whose first field after PyObject_HEAD is the embedded mpd_t. Because mpd_t carries its own coefficient storage pointer, the object also tracks whether that storage is heap-allocated or uses the inline buffer.

// CPython: Modules/_decimal/_decimal.c:120 PyDecObject
typedef struct {
PyObject_HEAD
PyObject *context; /* owning context or NULL for thread-local */
mpd_t dec; /* mpdecimal value; coefficient may be heap */
} PyDecObject;

decimal_new (line 520) accepts int, str, float, Decimal, and a 3-tuple (sign, digits, exponent). Each branch converts its input to an mpd_t using the appropriate mpdecimal constructor, then raises any pending signal as a Python exception through the trap mechanism.

// CPython: Modules/_decimal/_decimal.c:520 decimal_new
static PyObject *
decimal_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
/* ... dispatch on input type, call mpd_set_string / mpd_set_i64 / etc. */
SUBNORMAL_COEFF(v, ctx); /* mpdecimal signal -> Python exception */
}

PyDec_Type is registered as the decimal.Decimal type. Its tp_richcompare, tp_as_number, and tp_as_sequence slots all delegate to mpdecimal operations, checking traps after each call.

Arithmetic and trap mapping

PyDec_Add, PyDec_Mul, and PyDec_Div follow the same three-step pattern: acquire the thread-local context, call the mpdecimal primitive (e.g. mpd_qadd), then inspect status against the context's traps bitmask.

// CPython: Modules/_decimal/_decimal.c:1832 PyDec_Add
static PyObject *
PyDec_Add(PyObject *v, PyObject *w)
{
mpd_context_t *ctx;
uint32_t status = 0;

CONVERT_BINOP_RAISE(&a, &b, v, w, ctx);
mpd_qadd(MPD(result), MPD(a), MPD(b), ctx, &status);
if (dec_addstatus(result, status, ctx)) { /* trap check */
Py_DECREF(result);
return NULL;
}
return result;
}

dec_addstatus (line 340) converts a non-zero status word into the matching Python exception by walking the DecimalException hierarchy. The hierarchy is built at module init time from a table that pairs each mpd_err_t flag with a Python type object.

DecimalException hierarchy and context get/set

The exception classes (InvalidOperation, DivisionByZero, Overflow, Underflow, Subnormal, Inexact, Rounded, Clamped, FloatOperation) are created by PyErr_NewException during _decimal module initialisation and stored in the module state. They mirror the mpdecimal MPD_* constants one-for-one.

// CPython: Modules/_decimal/_decimal.c:290 DecimalException init
static int
init_decimal_exception_hierarchy(PyObject *m)
{
/* Each entry maps an mpd_err_t flag to a class name and base type. */
for (int i = 0; signal_map[i].name != NULL; i++) {
PyObject *base = signal_map[i].base ? signal_map[i].base : PyExc_ArithmeticError;
signal_map[i].ex = PyErr_NewException(signal_map[i].name, base, NULL);
/* ... */
}
}

PyDec_Context_Get (line 3900) returns the thread-local Context. If no thread-local context has been set, it falls back to the module-level default context. PyDec_Context_Set (line 3980) writes to the thread state's dict under the key "decimal_context".

// CPython: Modules/_decimal/_decimal.c:3900 PyDec_Context_Get
static PyObject *
PyDec_Context_Get(PyObject *self, PyObject *args)
{
PyObject *tl_context;
tl_context = PyDict_GetItemWithError(tstate->dict, tl_key);
if (tl_context == NULL) {
tl_context = module_state->default_context;
}
Py_INCREF(tl_context);
return tl_context;
}

gopy notes

Status: not yet ported.

Planned package path: module/decimal/.

The main porting challenge is the mpdecimal dependency. Options are: cgo-linking against the system libmpdec, vendoring mpdecimal as a cgo sub-package, or writing a pure-Go IEEE 754-2008 decimal engine. The last option gives the cleanest build graph but is the largest upfront investment. A cgo bridge is the fastest path to compatibility.

The DecimalException hierarchy must be replicated as Go error types that satisfy objects.Object so the VM can raise them through the normal exception path. The thread-local context maps naturally to a goroutine-local value stored in the vm.Frame chain.

The usedforsecurity parameter is not present in _decimal (that belongs to _hashlib), so there is no FIPS concern here.