python/cpython · error · TypeError

Cannot hash a signaling NaN value.

Error message

Cannot hash a signaling NaN value.

What it means

hash() of a signaling NaN (sNaN) raises TypeError. Quiet NaNs hash via object identity and infinities hash to _PyHASH_INF/-_PyHASH_INF, but sNaN is defined to trap on any use in an operation, and hashing counts as such a use. This keeps sNaN out of dict keys and sets.

Source

Thrown at Lib/_pydecimal.py:908

        # Compare(NaN, NaN) = NaN
        if (self._is_special or other and other._is_special):
            ans = self._check_nans(other, context)
            if ans:
                return ans

        return Decimal(self._cmp(other))

    def __hash__(self):
        """x.__hash__() <==> hash(x)"""

        # In order to make sure that the hash of a Decimal instance
        # agrees with the hash of a numerically equal integer, float
        # or Fraction, we follow the rules for numeric hashes outlined
        # in the documentation.  (See library docs, 'Built-in Types').
        if self._is_special:
            if self.is_snan():
                raise TypeError('Cannot hash a signaling NaN value.')
            elif self.is_nan():
                return object.__hash__(self)
            else:
                if self._sign:
                    return -_PyHASH_INF
                else:
                    return _PyHASH_INF

        if self._exp >= 0:
            exp_hash = pow(10, self._exp, _PyHASH_MODULUS)
        else:
            exp_hash = pow(_PyHASH_10INV, -self._exp, _PyHASH_MODULUS)
        hash_ = int(self._int) * exp_hash % _PyHASH_MODULUS
        ans = hash_ if self >= 0 else -hash_
        return -2 if ans == -1 else ans

    def as_tuple(self):
        """Represents the number as a triple tuple.

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Convert signaling NaNs to quiet NaNs after parsing: d = d.copy_abs() is not enough — use d + Decimal(0) inside a trapped-snan context, or Context(Emax, traps={InvalidOperation: False}) arithmetic that quiets it, e.g. ctx.multiply(d, 1)
  2. Filter sNaN before hashing: if isinstance(d, Decimal) and d.is_snan(): skip/replace
  3. Raise FloatOperation/InvalidOperation traps deliberately at parse time so sNaN never enters your pipeline silently

Example fix

// before
uniq = set(rows)  # TypeError if any row is Decimal('sNaN')

// after
from decimal import Decimal
def quiet(d):
    return d.fma(1, 0) if d.is_snan() else d  # fma quiets sNaN? use context mul
uniq = {quiet(d) for d in rows}
Defensive patterns

Strategy: validation

Validate before calling

from decimal import Decimal, localcontext, ExtendedContext

def hashable_decimal(d: Decimal) -> Decimal:
    if d.is_snan():
        with localcontext(ExtendedContext) as ctx:
            return (d * 1)  # arithmetic quiets sNaN to NaN
    return d

# uniq = {hashable_decimal(d) for d in rows}

Type guard

from decimal import Decimal

def is_hashable_decimal(d) -> bool:
    return not (isinstance(d, Decimal) and d.is_snan())

Try / catch

try:
    h = hash(d)
except TypeError:
    # sNaN in a key position: treat as invalid data
    raise ValueError('signaling NaN cannot be used as a key') from None

Prevention

When it happens

Trigger: hash(Decimal('sNaN')); Decimal('sNaN') in {Decimal('1')}; {'k': 1}[Decimal('sNaN7')] as a key; putting a list of parsed values containing sNaN into a set().

Common situations: Ingesting external data (FIX protocol prices, IEEE payloads) where sNaN survived parsing, then deduplicating rows with set() or building dict keys; test fixtures accidentally containing sNaN.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/6e316f5ee36f933e. Report an issue: GitHub.