При сравнении значений с плавающей запятой на равенство существует два разных подхода:
NaN
не равный самому себе, что соответствует спецификации IEEE 754 .NaN
быть равным самому себе, что обеспечивает математическое свойство рефлексивности, которое необходимо для определения отношения эквивалентности
Встроенный в IEEE с плавающей точкой типа в C # ( float
и double
) следует IEEE семантике ==
и !=
(и реляционные операторы , как <
) , но обеспечить возвратность для object.Equals
, IEquatable<T>.Equals
(и CompareTo
).
Теперь рассмотрим библиотеку , которая обеспечивает вектор структур сверху float
/ double
. Такой тип вектора перегрузит ==
/ !=
и переопределит object.Equals
/ IEquatable<T>.Equals
.
Все согласны с тем, что ==
/ !=
должны следовать семантике IEEE. Вопрос в том, должна ли такая библиотека реализовывать Equals
метод (который отделен от операторов равенства) рефлексивным способом или способом, соответствующим семантике IEEE.
Аргументы для использования семантики IEEE для Equals
:
- Следует IEEE 754
Это (возможно, намного) быстрее, потому что он может использовать SIMD инструкции
Я задал отдельный вопрос о стековом потоке о том, как вы выражаете рефлексивное равенство с помощью инструкций SIMD и их влияние на производительность: инструкции SIMD для сравнения равенства с плавающей запятой
Обновление: кажется, что можно эффективно реализовать рефлексивное равенство, используя три SIMD-инструкции.
Документация для
Equals
не требует рефлексивности при использовании плавающей запятой:Следующие операторы должны быть истинными для всех реализаций метода Equals (Object). В списке
x
,y
иz
представляют собой ссылку на объекты, которые не являются пустыми.x.Equals(x)
возвращаетtrue
, кроме случаев, когда используются типы с плавающей точкой. См. ISO / IEC / IEEE 60559: 2011, Информационные технологии. Микропроцессорные системы. Арифметика с плавающей точкой.Если вы используете float в качестве словарных ключей, вы живете в состоянии греха и не должны ожидать нормального поведения.
Аргументы за рефлексивность:
Это согласуется с существующими типами, в том числе
Single
,Double
,Tuple
иSystem.Numerics.Complex
.Я не знаю ни одного прецедента в BCL, где
Equals
следует IEEE вместо того, чтобы быть рефлексивным. Счетчик примеры включаютSingle
,Double
,Tuple
иSystem.Numerics.Complex
.Equals
в основном используется контейнерами и алгоритмами поиска, основанными на рефлексивности. Для этих алгоритмов выигрыш в производительности не имеет значения, если мешает им работать. Не жертвуйте правильностью ради производительности.- Он ломает все хэш на основе наборов и словари,
Contains
,Find
,IndexOf
на различных сборниках / LINQ, набор на основе LINQ операции (Union
,Except
и т.д.) , если данные содержатNaN
значение. Код, который выполняет фактические вычисления, где семантика IEEE является приемлемой, обычно работает на конкретных типах и использует
==
/!=
(или, более вероятно, сравнения эпсилона).В настоящее время вы не можете писать высокопроизводительные вычисления с использованием шаблонов, поскольку для этого вам нужны арифметические операции, но они не доступны через интерфейсы / виртуальные методы.
Поэтому более медленный
Equals
метод не повлияет на большинство высокопроизводительных программ.Можно предоставить
IeeeEquals
метод или методIeeeEqualityComparer<T>
для случаев, когда вам нужна семантика IEEE или вам нужно повысить производительность.
На мой взгляд, эти аргументы решительно поддерживают рефлексивную реализацию.
Команда Microsoft CoreFX планирует представить такой векторный тип в .NET. В отличие от меня они предпочитают решение IEEE , в основном из-за преимуществ в производительности. Поскольку такое решение, безусловно, не будет изменено после окончательного релиза, я хочу получить отзывы сообщества, что я считаю большой ошибкой.
источник
==
иEquals
будет давать разные результаты. Многие программисты предполагают, что это так, и делают то же самое . Кроме того, в общем случае реализации операторов равенства вызываютEquals
метод. Вы утверждали, что можно включитьIeeeEquals
, но можно сделать и наоборот и включитьReflexiveEquals
-метод. ТипVector<float>
может использоваться во многих приложениях, критичных к производительности, и должен быть соответственно оптимизирован.float
/double
и некоторых других типов,==
иEquals
они уже разные. Я думаю, что несоответствие с существующими типами будет еще более запутанным, чем несоответствие между ними,==
иEquals
вам все равно придется иметь дело с другими типами. 2) Практически все универсальные алгоритмы / коллекции используютEquals
и полагаются на его рефлексивность для функций (LINQ и словари), тогда как конкретные алгоритмы с плавающей точкой обычно используют==
там, где они получают свою семантику IEEE.Vector<float>
другим "зверем", чем простымfloat
илиdouble
. По той мере, я не могу понять причинуEquals
или==
оператор соблюдать стандарты них. Вы сказали себе: «Если вы используете float в качестве словарных ключей, вы живете в состоянии греха и не должны ожидать нормального поведения». Если кто-то хранитNaN
в словаре, то это их собственная чертова вина за использование ужасной практики. Я не думаю, что команда CoreFX не продумала это до конца. Я бы пошел сReflexiveEquals
или аналогичным, только для производительности.Ответы:
Я бы сказал, что поведение IEEE правильное.
NaN
s не эквивалентны друг другу в любом случае; они соответствуют плохо определенным условиям, в которых числовой ответ не подходит.Помимо преимуществ в производительности, которые дает использование арифметики IEEE, которую большинство процессоров поддерживают изначально, я думаю, что существует семантическая проблема с утверждением, что если
isnan(x) && isnan(y)
, тоx == y
. Например:Я бы сказал, что нет веской причины, по которой можно считать
x
равнымy
. Вы вряд ли могли бы заключить, что они являются эквивалентными числами; они вовсе не числа, так что это кажется совершенно неверным понятием.Кроме того, с точки зрения разработки API, если вы работаете с универсальной библиотекой, которая предназначена для использования многими программистами, имеет смысл использовать наиболее типичную для отрасли семантику с плавающей точкой. Цель хорошей библиотеки - сэкономить время для тех, кто ею пользуется, поэтому создание нестандартного поведения созрело для путаницы.
источник
NaN == NaN
должно вернуть ложь неоспоримо. Вопрос в том, что.Equals
должен делать метод. Например, если я используюNaN
в качестве ключа словаря, связанное значение становится необратимым, еслиNaN.Equals(NaN)
возвращать false.Single
,Double
и т.д. классы уже имеют рефлекторное поведение. ИМХО, это было просто неправильное решение для начала. Но я бы не позволил элегантности встать на пути полезности / скорости.==
которые всегда следовали IEEE, поэтому они получали бы быстрый код независимо от того, какEquals
он реализован. IMO весь смысл наличия отдельногоEquals
метода заключается в использовании в алгоритмах, которые не заботятся о конкретном типе, таком какDistinct()
функция LINQ .==
оператор иEquals()
функция с разной семантикой. Я думаю, что вы платите за возможную путаницу с точки зрения разработчика, без реальной выгоды (я не назначаю никакого значения возможности использовать вектор чисел в качестве словарного ключа). Это только мое мнение; Я не думаю, что есть объективный ответ на данный вопрос.Существует проблема: IEEE754 определяет реляционные операции и равенство способом, который хорошо подходит для числовых приложений. Он плохо подходит для сортировки и хеширования. Поэтому, если вы хотите отсортировать массив на основе числовых значений или добавить числовые значения в набор или использовать их в качестве ключей в словаре, вы либо объявляете, что значения NaN недопустимы, либо не используете IEEE754. встроенные операции. Ваша хеш-таблица должна была бы удостовериться, что все NaN совпадают с одним и тем же значением, и сравнить их друг с другом.
Если вы определяете Vector, то вам нужно принять решение о том, хотите ли вы использовать его только для числовых целей или он должен быть совместим с сортировкой и хэшированием. Я лично считаю, что численное назначение должно быть гораздо важнее. Если требуется сортировка / хеширование, вы можете написать класс с Vector в качестве члена и определить хеширование и равенство в этом классе так, как вам нравится.
источник
==
и!=
операторы для них. По моему опыту, этотEquals
метод в основном используется не числовыми алгоритмами.