{"record":{"id":"6e316f5ee36f933e","repo":"python/cpython","slug":"cannot-hash-a-signaling-nan-value","errorCode":null,"errorMessage":"Cannot hash a signaling NaN value.","messagePattern":"Cannot hash a signaling NaN value\\.","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"Lib/_pydecimal.py","lineNumber":908,"sourceCode":"\n        # Compare(NaN, NaN) = NaN\n        if (self._is_special or other and other._is_special):\n            ans = self._check_nans(other, context)\n            if ans:\n                return ans\n\n        return Decimal(self._cmp(other))\n\n    def __hash__(self):\n        \"\"\"x.__hash__() <==> hash(x)\"\"\"\n\n        # In order to make sure that the hash of a Decimal instance\n        # agrees with the hash of a numerically equal integer, float\n        # or Fraction, we follow the rules for numeric hashes outlined\n        # in the documentation.  (See library docs, 'Built-in Types').\n        if self._is_special:\n            if self.is_snan():\n                raise TypeError('Cannot hash a signaling NaN value.')\n            elif self.is_nan():\n                return object.__hash__(self)\n            else:\n                if self._sign:\n                    return -_PyHASH_INF\n                else:\n                    return _PyHASH_INF\n\n        if self._exp >= 0:\n            exp_hash = pow(10, self._exp, _PyHASH_MODULUS)\n        else:\n            exp_hash = pow(_PyHASH_10INV, -self._exp, _PyHASH_MODULUS)\n        hash_ = int(self._int) * exp_hash % _PyHASH_MODULUS\n        ans = hash_ if self >= 0 else -hash_\n        return -2 if ans == -1 else ans\n\n    def as_tuple(self):\n        \"\"\"Represents the number as a triple tuple.","sourceCodeStart":890,"sourceCodeEnd":926,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_pydecimal.py#L890-L926","documentation":"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.","triggerScenarios":"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().","commonSituations":"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.","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"],"exampleFix":"// before\nuniq = set(rows)  # TypeError if any row is Decimal('sNaN')\n\n// after\nfrom decimal import Decimal\ndef quiet(d):\n    return d.fma(1, 0) if d.is_snan() else d  # fma quiets sNaN? use context mul\nuniq = {quiet(d) for d in rows}","handlingStrategy":"validation","validationCode":"from decimal import Decimal, localcontext, ExtendedContext\n\ndef hashable_decimal(d: Decimal) -> Decimal:\n    if d.is_snan():\n        with localcontext(ExtendedContext) as ctx:\n            return (d * 1)  # arithmetic quiets sNaN to NaN\n    return d\n\n# uniq = {hashable_decimal(d) for d in rows}","typeGuard":"from decimal import Decimal\n\ndef is_hashable_decimal(d) -> bool:\n    return not (isinstance(d, Decimal) and d.is_snan())","tryCatchPattern":"try:\n    h = hash(d)\nexcept TypeError:\n    # sNaN in a key position: treat as invalid data\n    raise ValueError('signaling NaN cannot be used as a key') from None","preventionTips":["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"],"tags":["decimal","hash","snan","nan","typeerror","set"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}