decimal: as_tuple()'s exponent Is a String for NaN/Infinity (tested)

Decimal.as_tuple() decomposes a value into (sign, digits, exponent) -- a common way to serialize a Decimal to JSON or rebuild its numeric value by hand. We measured what that tuple looks like for the special values NaN and Infinity, which is easy to miss if testing only covers ordinary numbers.

1. For a normal finite value, exponent is an int

from decimal import Decimal

t = Decimal("123.45").as_tuple()
print(t)
print(type(t.exponent))
DecimalTuple(sign=0, digits=(1, 2, 3, 4, 5), exponent=-2)
<class 'int'>

Measured: for an ordinary finite Decimal, as_tuple() gives a sign of 0 or 1, a digits tuple of individual decimal digits, and an exponent that's a plain int -- the value is sign * int(digits) * 10**exponent. This is the shape most code implicitly assumes.

2. For NaN and Infinity, exponent is a string, not an int

from decimal import Decimal

for val in (Decimal("NaN"), Decimal("sNaN"), Decimal("Infinity"), Decimal("-Infinity")):
    t = val.as_tuple()
    print(val, "->", t, type(t.exponent))
NaN -> DecimalTuple(sign=0, digits=(), exponent='n') <class 'str'>
sNaN -> DecimalTuple(sign=0, digits=(), exponent='N') <class 'str'>
Infinity -> DecimalTuple(sign=0, digits=(0,), exponent='F') <class 'str'>
-Infinity -> DecimalTuple(sign=1, digits=(0,), exponent='F') <class 'str'>

Measured: for these four special values, the exponent field is one of the single-character strings 'n' (quiet NaN), 'N' (signaling NaN), or 'F' (Infinity) -- never an int. digits is also not usable the normal way: empty for both NaN variants, and a meaningless placeholder (0,) for Infinity. Nothing about the field names or types warns that exponent can switch types depending on the value.

3. The risk: code that reconstructs the value crashes on NaN/Infinity, not on bad input

from decimal import Decimal

def to_scaled_int(d):
    sign, digits, exponent = d.as_tuple()
    value = int("".join(map(str, digits)) or "0")
    return (-1 if sign else 1) * value * 10 ** exponent

print(to_scaled_int(Decimal("123.45")))
print(to_scaled_int(Decimal("NaN")))
12345
Traceback (most recent call last):
  ...
TypeError: unsupported operand type(s) for ** or pow(): 'int' and 'str'

Measured: a helper that decomposes and rescales a Decimal (for storage as a scaled integer, a database column, or a custom JSON encoder) works fine for every ordinary value and then raises a TypeError the moment a NaN or Infinity reaches it -- not because the input was malformed, but because as_tuple()'s own contract changes shape for exactly the values most likely to show up unannounced from a prior calculation (a disabled trap, a bad division). Guard with d.is_finite() before decomposing, or check isinstance(exponent, int).

Frequently asked questions

What does Decimal.as_tuple() return for NaN or Infinity?

We measured the exponent field being a string -- 'n' for quiet NaN, 'N' for signaling NaN, and 'F' for Infinity -- instead of the int it is for every finite value. digits is also empty for NaN and a meaningless (0,) for Infinity.

Why does my code crash with 'unsupported operand type for **' after calling Decimal.as_tuple()?

Because you're reconstructing the value with digits * 10**exponent and exponent came back as a string ('n', 'N', or 'F') instead of an int -- we confirmed this happens specifically for NaN and Infinity Decimal values, not for ordinary finite ones.

How do I safely decompose a Decimal that might be NaN or Infinity?

Check d.is_finite() before calling as_tuple() and reconstructing the value, or check isinstance(exponent, int) after. We measured is_finite() as the correct guard since it's False for exactly the NaN/sNaN/Infinity cases where exponent becomes a string.

Is the exponent from Decimal.as_tuple() always an int?

No -- we measured it as an int only for finite Decimal values. For NaN, sNaN, and Infinity it's a single-character string ('n', 'N', or 'F') instead, which breaks any arithmetic that assumes an int exponent.

Does as_tuple() work the same for Infinity as for a regular large number?

No -- we measured Decimal('Infinity').as_tuple() returning digits=(0,) and exponent='F', not a tuple of actual digits with an int exponent like a large finite Decimal would. The (0,) is a placeholder, not meaningful data.

Written with AI assistance. Measured on 2026-10-04 with Python 3.13.5's built-in decimal module: called as_tuple() on a normal finite Decimal, then on Decimal('NaN'), Decimal('sNaN'), and Decimal('Infinity')/Decimal('-Infinity'), and inspected the type of the returned exponent field in each case. PlainHeaders is our extension; Python's decimal module is built in.