Why does float('nan') == float('nan') return False, and how do you detect nan?
answer
- IEEE 754 calls it unordered
- Every comparison with it is false
- Even equality with itself fails
- x != x is the old idiom
- math.isnan, or math.isfinite for both cases
basics
~10 sIEEE 754 defines nan as unordered: every comparison involving it is false, including equality with itself. Detect it with math.isnan(x), or use math.isfinite(x) to reject nan and both infinities in one check.
solid answer
~50 snan means "not a number", the IEEE 754 result of an undefined operation, and the standard makes every comparison against it false - `==`, `<` and `>` all return False, so `x != x` is True only when x is nan. That is why equality cannot test for it and `math.isnan(x)` exists; `math.isfinite(x)` is the stronger check that also rejects `math.inf` and `-math.inf`. The practical damage is downstream: `sorted()`, `max()` and `min()` rely on comparisons that are all false, so a single nan silently produces a wrong ordering that depends on where it sat in the input. Containers add a second surprise - `in`, and dict lookup, test identity before equality, so the *same* nan object is found in a list while an equal-looking one built separately is not. Screen with `math.isfinite` at the boundary where the data arrives.
code
python · 7 linesimport math
nan = float('nan')
print(nan == nan, math.isnan(nan)) # False True
print(nan in [nan]) # True: the same object, found by identity
print(nan in [float('nan')]) # False: a different nan object
print(sorted([3.0, nan, 1.0, 2.0])) # [3.0, nan, 1.0, 2.0] - untouchedgo deeper
Remember that nan is never equal to anything, including another nan, and that math.isnan(x) is the way to test for it. Knowing that float('nan') creates one is enough at this level.
Explain the IEEE 754 unordered rule, why x != x works as a detector, and the difference between math.isnan, math.isinf and math.isfinite. Be able to show what a nan does to sorted() or max().
Focus on silent corruption: aggregates and orderings that degrade without raising, tolerance checks that report a mismatch instead of bad data, and containers whose identity fast path makes membership inconsistent. Screen at ingest, not at use.
Decide the contract for non-finite values across services - rejected at the boundary, encoded as an explicit missing marker, or allowed through - and make sure serialization formats and schemas enforce that choice rather than leaving each consumer to guess.
## What nan is nan is a special IEEE 754 float value meaning "not a number": the defined result of an operation with no meaningful answer. `math.nan` is a ready-made one, `float('nan')` parses another, and arithmetic produces them - `math.inf - math.inf`, `math.inf * 0.0`, and `math.inf / math.inf` all evaluate to nan. Note that Python does **not** produce nan or inf from division by zero the way C does: `1.0 / 0.0` raises `ZeroDivisionError`. In practice nans arrive from parsed input, from serialized data, from libraries that use them as a missing-value marker, and from infinity arithmetic. ## Why it compares unequal to itself The standard classifies nan as *unordered* with respect to every value including itself. So `nan == nan` is False, `nan < 1.0` is False, and `nan > 1.0` is also False - the two are not equal, and neither is greater. This is deliberate: nan represents "the answer is undefined", and claiming two undefined results are the same value would be a stronger statement than the computation supports. One consequence is the classic idiom `x != x`, which is True for nan and for nothing else. It works, but `math.isnan(x)` says what it means. For a value that may be nan, inf or finite, `math.isfinite(x)` answers the question you usually want in one call, and `math.isinf(x)` isolates the infinities. ## Why detection matters more than the trivia The equality rule is quiet. The failures it causes are not. **Ordering silently breaks.** `sorted()`, `min()` and `max()` are built on pairwise comparisons that are all False when a nan is involved, so the algorithm keeps whatever it happened to be holding. `sorted([3.0, nan, 1.0, 2.0])` returns `[3.0, nan, 1.0, 2.0]` - unchanged and unsorted, with no error. `max([nan, 1.5, 2.5])` returns nan while `max([1.5, 2.5, nan])` returns 2.5. Same data, different order, different answer. **Tolerance comparison does not save you.** `math.isclose(nan, nan)` is False, so a reconciliation check that would otherwise pass reports a mismatch, and the report says "these totals differ" rather than "this value is not a number". **Containers behave inconsistently.** `list.__contains__` and dict lookup test identity first as a fast path, then equality. So the *same* nan object is found - `n = float('nan'); n in [n]` is True - while a separately constructed nan with an identical bit pattern is not. A nan used as a dict key can be retrieved with the original object and never with an equal-looking one, and a set can end up holding several nans that all "should" be duplicates. That identity shortcut is also why `[nan] == [nan]` is True for one shared object: list equality compares elements identity-first. ## Infinity, for contrast `math.inf` is ordered normally: it compares greater than every finite float, `math.isinf` detects it, and `math.inf == math.inf` is True. It is well behaved right up until arithmetic makes it undefined - `inf - inf` and `inf * 0.0` both give nan. Infinities are also the usual *source* of a nan in a pipeline that started with clean data: an overflow produces inf, a later subtraction produces nan, and the nan then poisons every aggregate downstream. ## The defensive pattern Screen at the boundary, not at the point of use: ```python import math clean = [x for x in readings if math.isfinite(x)] ``` Decide explicitly what a non-finite value means for the domain - drop it, substitute a default, or fail the batch loudly - and do it where the data enters, so no aggregation ever sees one. Filtering deep inside a computation means every intermediate result already has to be nan-aware. If a value must never be nan, assert it on ingest; a `ValueError` at parse time is worth far more than a silently wrong ordering three services later. ## The interview point Anyone can recite "nan is not equal to itself". The question separating levels is what breaks because of it. A strong answer names `math.isnan` and `math.isfinite`, explains that ordering and aggregation degrade silently rather than raising, mentions the identity fast path in containers, and describes screening at ingest as the fix.
- Why is float('nan') in [float('nan')] False while n = float('nan'); n in [n] is True?Containment on a list compares each element with an identity check first and only falls back to ==. The same object passes the identity test, so it is reported as present even though equality would say otherwise. Two separately built nans are different objects and equality then fails, so the answer is False. Dict and set lookup use the same identity-then-equality fast path.
- What happens if a nan reaches sorted() or max()?Nothing visible, which is the problem. Every comparison involving nan is False, so the algorithms keep whatever they were holding and produce an order that depends on where the nan sat in the input. sorted() can return the list essentially unchanged, and max() returns nan or a real value depending on position. There is no exception, so screen with math.isfinite before aggregating.
- Where do nan values come from if 1.0 / 0.0 raises in Python?From parsed or deserialized input containing 'nan', from libraries that use nan as a missing-value marker, and from arithmetic on infinities - math.inf - math.inf and math.inf * 0.0 both give nan. Overflow is the common upstream cause: a computation reaches inf, a later subtraction turns it into nan, and the nan then contaminates every aggregate downstream.
saying these in an interview costs you the question
- Tests for nan by comparing against float('nan')
- Assumes sorted() raises or orders nan sensibly
- Thinks math.isclose(nan, nan) returns True
- Believes 1.0 / 0.0 yields inf in Python
- Confuses math.isnan with math.isfinite
- Stores nan as a dict key and expects reliable lookup