Какие последствия имеет использование NaN как элемента множества в Python?
NaN (not a number) не равен даже самому себе: выражение сравнения с ним возвращает False. Поэтому NaN нельзя надёжно обнаруживать через сравнение значение == NaN, а множество может содержать несколько разных объектов NaN; для проверки следует использовать math.isnan().
NaN появился в стандарте IEEE 754 для представления результата неопределённой или недопустимой арифметической операции, например нулевого деления в вычислениях с плавающей точкой. Стандарт специально сделал NaN неравным любому значению, включая другой NaN, чтобы ошибочные числовые результаты не выглядели обычными числами.
Python следует этой семантике для типа float. Поэтому поведение NaN определяется не особенностями множеств, а правилами сравнения чисел с плавающей точкой.
Если программа проверяет результат вычисления сравнением с NaN, проверка не сработает: NaN не равен самому себе. Аналогично, операции сортировки, дедупликации и поиска могут давать неожиданные результаты, если NaN обрабатывается как обычное числовое значение.
У множеств и словарей есть дополнительная особенность: одинаковый объект может быть найден по идентичности, даже если его сравнение с самим собой возвращает False. При этом разные объекты NaN не считаются равными и могут существовать в одном множестве одновременно.
Для обнаружения NaN используют math.isnan(value), а не сравнение с результатом float("nan"). Само значение NaN обычно получают из вычислений или преобразованием строки.
Последняя строка не противоречит правилу сравнения: проверка членства контейнера учитывает идентичность объекта. Вторая запись создаёт другой объект NaN; он не равен первому, поэтому множество сохраняет обе записи.
NaN также плохо подходит для обычной сортировки: сравнения «меньше» и «больше» с ним возвращают False, поэтому итоговый порядок может зависеть от исходной последовательности. Если NaN допустим в данных, его обычно явно обрабатывают до сортировки, группировки или сравнения.
Важно отличать NaN от бесконечности. float("inf") равен самому себе и участвует в сравнениях как специальное бесконечное числовое значение, тогда как NaN обозначает отсутствие определённого числового результата.
Сервис получает измерения датчика и складывает их в множество для удаления повторов. В потоке встречаются несколько NaN, обозначающих неисправность датчика. Простое преобразование в множество не удаляет все такие значения, потому что разные NaN не равны друг другу.
Рассматривались два варианта. Первый — сравнивать значения с NaN; он неверен, поскольку такое сравнение всегда ложно. Второй — перед добавлением разделять значения на числовые и NaN с помощью math.isnan(), а для NaN использовать отдельный признак или единственный маркер пропущенного значения.
Выбран второй вариант: NaN обрабатывается явно до дедупликации. Это делает правила данных предсказуемыми, не зависит от деталей хеширования и не смешивает «неизвестное значение» с обычным числом.
Да, NaN является хешируемым объектом и может быть ключом словаря. Однако два разных объекта NaN не считаются равными, поэтому они могут образовать разные ключи. Надёжнее не использовать NaN как логический идентификатор, а нормализовать его, например заменять на None или специальный объект.
value == float("nan") никогда не обнаруживает NaN?Каждый операнд участвует в сравнении как значение NaN, а NaN по правилам IEEE 754 не равен самому себе и любому другому значению. Кроме того, каждый вызов float("nan") обычно создаёт отдельный объект. Для проверки нужно вызвать math.isnan(value) после подтверждения, что значение имеет числовой тип, допускающий такую проверку.
Он делает обычную сортировку семантически ненадёжной, потому что сравнения с NaN не задают нормального полного порядка. Результат может выглядеть частично отсортированным, но нельзя считать его корректным порядком чисел. Если порядок важен, NaN сначала выделяют отдельно или используют ключ сортировки, который явно помещает NaN в начало либо в конец.