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
- 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)
- Filter sNaN before hashing: if isinstance(d, Decimal) and d.is_snan(): skip/replace
- 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
- Filter or quiet sNaN values right after parsing external data
- Never put raw Decimals from untrusted feeds directly into set/dict keys
- Test fixtures should cover sNaN input so hashing paths are exercised
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
- Cannot convert signaling NaN to float
- Cannot convert %r to Decimal
- cannot convert NaN to integer ratio
- Cannot convert NaN to integer
- Second argument to round should be integral
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/6e316f5ee36f933e.
Report an issue: GitHub.