При сравнении значений с плавающей запятой на равенство существует два разных подхода:
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 , в основном из-за преимуществ в производительности. Поскольку такое решение, безусловно, не будет изменено после окончательного релиза, я хочу получить отзывы сообщества, что я считаю большой ошибкой.
float
/ double
и некоторых других типов, ==
и Equals
они уже разные. Я думаю, что несоответствие с существующими типами будет еще более запутанным, чем несоответствие между ними, ==
и Equals
вам все равно придется иметь дело с другими типами. 2) Практически все универсальные алгоритмы / коллекции используют Equals
и полагаются на его рефлексивность для функций (LINQ и словари), тогда как конкретные алгоритмы с плавающей точкой обычно используют ==
там, где они получают свою семантику IEEE.
Vector<float>
другим "зверем", чем простым float
или double
. По той мере, я не могу понять причину Equals
или ==
оператор соблюдать стандарты них. Вы сказали себе: «Если вы используете float в качестве словарных ключей, вы живете в состоянии греха и не должны ожидать нормального поведения». Если кто-то хранит NaN
в словаре, то это их собственная чертова вина за использование ужасной практики. Я не думаю, что команда CoreFX не продумала это до конца. Я бы пошел с ReflexiveEquals
или аналогичным, только для производительности.
==
иEquals
будет давать разные результаты. Многие программисты предполагают, что это так, и делают то же самое . Кроме того, в общем случае реализации операторов равенства вызываютEquals
метод. Вы утверждали, что можно включитьIeeeEquals
, но можно сделать и наоборот и включитьReflexiveEquals
-метод. ТипVector<float>
может использоваться во многих приложениях, критичных к производительности, и должен быть соответственно оптимизирован.