Похоже, что в Numpy 2 появился свой отдельный float64(nan)
, отличный от np.nan
. Теперь регрессионные тесты терпят неудачу, потому что пока
>>> np.nan is np.nan
True
у нас теперь есть
>>> np.nan is np.float64("nan")
False
(также json не может сериализовать эти новые NaN
).
Почему они внесли это изменение?
Есть ли способ автоматически преобразовать числа с плавающей запятой в обычные числа с плавающей запятой?
@JérômeRichard- спасибо, я исправил опечатку
Какие изменения? Я тоже получаю False
с NumPy 1.
np.nan
имеет тип float
, что немного удивительно (не тип Numpy). Таким образом, это не будет то же самое, что np.float32("nan")
или np.float64("nan")
. Однако, как ни странно, np.nan is float("nan")
тоже не верно... Может быть, NaN Python сигнализируют NaN, а Numpy молчат NaN? Обратите внимание, что сравнение NaN обычно является очень плохой идеей. Действительно, NaN никогда не равен самому себе, и существует много разных NaN (даже тихих NaN). Лучше использовать np.isnan
. Кстати, я тоже получаю False
с Numpy 1.24.3 (в Windows).
@JérômeRichard: У float("nan")
нет причин возвращать конкретный объект numpy.nan
. Вы бы увидели то же самое, если бы попробовали 2.0 is float("2.0")
@user2357112 user2357112 Да, действительно, можно создать новый объект. Спасибо. Оно было бы равно, если бы значение было каким-то образом кэшировано (AFAIK другие языки, такие как Java, делают это для небольших целых чисел и, конечно же, для NaN, что влияет на некоторые реализации Python, но, очевидно, не на CPython). Возможно, выгода довольно мала.
Существует несколько NaN (см. ниже и NaN в качестве ссылки), и Python может повторно использовать объекты или нет. Использование X is Y
в рамках модульного теста является здесь основной проблемой.
Например, np.inf - np.inf
дает np.nan
:
np.inf-np.inf
# nan
И все еще:
np.nan is (np.inf-np.inf)
# False
Если вы хотите узнать, является ли объект NaN, всегда используйте np.isnan (или эквивалент math
или pandas
), никогда не сравнивайте объекты или is
:
np.isnan((np.inf-np.inf))
# True
NaN не кодируются одним способом. Могут быть тихие NaN (неоднозначная операция) или сигнальные NaN (ошибочная операция). Кроме того, Python может повторно использовать объекты… или нет.
При запуске np.nan is np.nan
Python использует один и тот же объект для обоих. Если вы запускаете np.log(-1) is np.log(-1)
на моей машине, это создает два разных объекта, и результат False
:
np.log(-1) is np.log(-1)
# False
Чтобы дать вам более сложный пример, на моей машине np.nan
всегда выдается один и тот же объект. Тихие/сигнальные NaN всегда создают разные объекты. Последовательное создание одного типа NaN иногда приводит к повторному использованию объекта, а иногда нет:
(id(np.nan), # 135051355677520
id(np.inf-np.inf), # 135046756258928
id(float('nan')), # 135046756258928
id(np.log(-1)), # 135046756256624 # triggers warning
id(np.sqrt(-1)), # 135046756256624 # triggers warning # same as previous
id(float('nan')), # 135046756258928 # same as before
id(np.sqrt(-1)), # 135046756261584 # triggers warning # different
id(np.nan), # 135051355677520 # same as before
)
При повторном запуске все идентификаторы меняются, но по той же схеме. np.nan
остается стабильным:
(id(np.nan), # 135051355677520
id(np.inf-np.inf), # 135046756261872
id(float('nan')), # 135046756261872
id(np.log(-1)), # 135046756262736 # triggers warning
id(np.sqrt(-1)), # 135046756262736 # triggers warning # same as previous
id(float('nan')), # 135046756261872 # same as before
id(np.sqrt(-1)), # 135046756262640 # triggers warning # different
id(np.nan), # 135051355677520 # same as before
)
Короче говоря, не делайте этого. Используйте np.isnan
Различия в выходных данных id
, которые вы видите, не имеют ничего общего с тихими/сигнальными NaN или другими различиями в кодировании NaN. np.nan
поддерживается ссылкой в numpy.__dict__
(и другими ссылками), поэтому его идентификатор нельзя повторно использовать для других объектов. np.inf-np.inf
и float('nan')
оба создают новые числа с плавающей запятой, и первый из них умирает до того, как будет создан второй, поэтому они оказываются в одной и той же ячейке памяти.
np.log(-1)
и np.sqrt(-1)
оба создают новые экземпляры numpy.float64
, а не float
. Распределитель размещает их в том же месте, что и друг друга, но в другом месте, чем обычное место с плавающей запятой, созданное np.inf-np.inf
и float('nan')
.
@user2357112 user2357112 я хотел сказать, что и то, и другое: NaN могут быть закодированы по-разному, а также могут быть повторно использованы как один и тот же объект… или нет (как в классическом Python).
Тогда вам, вероятно, следует это уточнить, поскольку текущая формулировка вашего ответа подразумевает, что фрагмент id
должен давать пример различий в кодировке.
Понятно, я уточню
@user2357112 user2357112 Надеюсь, теперь стало лучше. Как вы можете видеть, тихие или сигнальные NaN можно использовать повторно, если они создаются последовательно, но тихий NaN никогда не будет тем же объектом, что и сигнальный NaN.
Вы не показываете повторное использование объектов, вы просто показываете повторное использование памяти.
... но здесь нет сигнальных NaN. Предупреждения не поступают из-за сигнализации NaN — NumPy обнаруживает исключения IEEE 754 с плавающей запятой для выдачи этих предупреждений. Аналогичное предупреждение можно увидеть с помощью np.exp(10000)
, хотя такого понятия, как «сигнал бесконечности», не существует.
@user2357112 user2357112 Плохо, я проверил двоичное представление, и оба действительно являются тихими NaN (11111111110000000000000000000000
). Я знаю, что предупреждение инициируется самим Python, но я действительно думал, что были возвращены разные NaN. Попробую найти где я это прочитал. Следует отметить, что тип NaN не тот же самый: np.inf-np.inf
— это float
, np.sqrt(-1)
— это np.float64
. Важным сообщением остается то, что не следует использовать ==
или is
для сравнения NaN.
Numpy выдает ошибку на моей машине, потому что
np.float
устарело: «np.float
был устаревшим псевдонимом для встроенногоfloat
. Чтобы избежать этой ошибки в существующем коде, используйтеfloat
отдельно. Это не изменит никакого поведения и безопасно. Если вы специально если вам нужен скалярный тип numpy, используйтеnp.float64
здесь. Псевдонимы изначально устарели в NumPy 1.20. Более подробную информацию и рекомендации см. в исходном примечании к выпуску: numpy.org/devdocs/release/1.20.0-notes.html#deprecations;" . Использованиеnp.float
здесь особенно нецелесообразно, поскольку 32-битные NaN и 64-битные различаются.