Vector <float> .Equals应该是自反的还是应该遵循IEEE 754语义?


9

比较浮点值是否相等时,有两种不同的方法:

  • NaN不等于自身,这符合IEEE 754规范。
  • NaN等于自身,这提供了自反性的数学属性,这对于定义等价关系至关重要

在C#(内置的IEEE浮点类型floatdouble)遵循IEEE语义==!=(和关系运算符一样<),但确保反思object.EqualsIEquatable<T>.Equals(和CompareTo)。

现在考虑一个在float/ 之上提供矢量结构的库double。这样的向量类型将重载==/ !=并覆盖object.Equals/ IEquatable<T>.Equals

大家都同意==/ !=应该遵循IEEE语义。问题是,这样的库是否应该以Equals自反的方式或与IEEE语义相匹配的方式来实现该方法(与相等运算符分开)。

将IEEE语义用于Equals以下方面的参数:

  • 遵循IEEE 754
  • (可能更快),因为它可以利用SIMD指令

    我已经在stackoverflow上问了一个独立的问题,关于如何使用SIMD指令表达自反式相等性及其性能影响:用于浮点相等性比较的SIMD指令

    更新:似乎可以使用三个SIMD指令有效地实现自反性相等。

  • Equals涉及浮点的文档不需要反射:

    对于Equals(Object)方法的所有实现,以下语句必须为true。在列表中xyz代表对象引用不为空。

    x.Equals(x)返回true,除非涉及浮点类型。参见ISO / IEC / IEEE 60559:2011,信息技术-微处理器系统-浮点运算。

  • 如果您将浮点数用作字典键,那么您将生活在一种罪过的状态中,不应期望表现出理智的行为。

自反的观点:

  • 这是与现有的类型,包括一致的SingleDoubleTupleSystem.Numerics.Complex

    我不知道BCL中Equals遵循IEEE而不是反身的任何先例。相反的例子包括SingleDoubleTupleSystem.Numerics.Complex

  • Equals主要用于依赖自反性的容器和搜索算法。对于这些算法,如果阻止它们工作,则性能提升是无关紧要的。不要牺牲性能的正确性。
  • 它打破了所有基于散列集和词典,ContainsFindIndexOf各种收藏品/ LINQ,一套基于LINQ操作(UnionExcept,等),如果数据中包含NaN的值。
  • 在IEEE语义可接受的情况下执行实际计算的代码通常适用于具体类型并使用==/ !=(或更可能是epsilon比较)。

    您目前无法使用泛型编写高性能的计算,因为您需要为此进行算术运算,但是这些不能通过接口/虚拟方法使用。

    因此,较慢的Equals方法不会影响大多数高性能代码。

  • 对于需要IEEE语义或需要提高性能的情况,可以提供一种IeeeEquals方法或一种方法IeeeEqualityComparer<T>

我认为这些论点强烈支持反身执行。

微软的CoreFX团队计划在.NET中引入这种向量类型。与我不同,他们更喜欢IEEE解决方案,主要是因为性能方面的优势。由于这样的决定在最终发布后肯定不会改变,因此我想从社区中获得我认为是一个大错误的反馈。


1
优秀且发人深省的问题。对我而言(至少),这并没有意义,==并且Equals会返回不同的结果。许多程序员以为自己是,并且同样的事情。此外,通常,相等运算符的实现会调用该Equals方法。您认为一个可以包含一个-方法IeeeEquals,但是也可以ReflexiveEquals相反地包含-方法。该Vector<float>型可被用于许多性能关键应用,并且应该相应地最优化。
死于maus

@diemaus一些原因我没有找到有说服力的:1)float/ double等几个类型,==并且Equals都已经不同。我认为与现有类型的不一致甚至比之间的不一致更令人困惑==Equals您仍然需要处理其他类型。2)几乎所有通用算法/集合都使用Equals并依赖于其对功能的自反性(LINQ和字典),而具体的浮点算法通常==在获得IEEE语义的地方使用。
CodesInChaos

我会考虑Vector<float>一个不同于简单的float或的“野兽” double。通过这种方式,我看不出原因Equals==运营商遵守它们的标准。您对自己说:“如果您将浮点数用作字典键,那么您将生活在一种罪过的状态中,不应期望出现理智的行为”。如果将其存储NaN在字典中,那么使用可怕的做法是他们自己该死的错。我几乎不认为CoreFX团队没有考虑到这一点。ReflexiveEquals为了性能起见,我会选择一个或类似的产品。
死于maus 2016年

Answers:


5

我认为IEEE行为是正确的。NaNs在任何方面都不相等;它们对应于数字答案不合适的不确定条件。

除了大多数处理器本机支持的使用IEEE算术所带来的性能优势外,我认为说if isnan(x) && isnan(y),then 还有一个语义问题x == y。例如:

// C++
double inf = std::numeric_limits<double>::infinity();
double x = 0.0 / 0.0;
double y = inf - inf;

我认为没有理由认为x等于y。您几乎无法得出结论,它们是等价的。它们根本不是数字,所以这似乎完全是一个无效的概念。

此外,从API设计的角度来看,如果您正在使用打算供许多程序员使用的通用库,则使用最典型的行业浮点语义是有意义的。好的库的目的是为使用它的人们节省时间,因此建立非标准行为的时机已经成熟,容易引起混淆。


3
NaN == NaN应该返回false是无可争议的。问题是该.Equals方法应该做什么。例如,如果我将其NaN用作字典键,则如果NaN.Equals(NaN)返回false ,则关联的值将变得不可检索。
CodesInChaos

1
我认为您必须针对常见情况进行优化。数字向量的常见情况是高通量数值计算(通常使用SIMD指令进行优化)。我认为使用向量作为字典键是一种极为罕见的用例,并且几乎不值得设计您的语义。似乎最合理的,我的反驳是一致的,因为现有的SingleDouble等类已经有反身的行为。恕我直言,这只是一个错误的决定。但是我不会让优雅妨碍实用性/速度。
杰森R

但是数值计算通常会使用==始终遵循IEEE的方法,因此无论如何Equals实现,它们都将获得快速的代码。IMO Equals在一种不关心具体类型的算法(例如LINQ Distinct()函数)中使用了一种单独的方法。
CodesInChaos

1
我明白了。但是,我反对使用具有不同语义的==运算符和Equals()函数的API 。从开发人员的角度来看,我认为您付出了潜在的混乱代价,却没有任何真正的好处(我不会为使用数字向量作为字典键分配任何价值)。这只是我的意见;我认为目前的问题没有客观答案。
杰森R

0

有一个问题:IEEE754以一种非常适合数字应用程序的方式定义了关系运算和相等性。它不适用于排序和散列。因此,如果要基于数值对数组排序,或者要向集合中添加数值或将其用作字典中的键,则可以声明不允许使用NaN值,或者不使用IEEE754内置操作。您的哈希表必须确保所有NaN都匹配相同的值,并且彼此比较相等。

如果定义Vector,则必须做出设计决定,是只将其用于数字目的还是应与排序和哈希兼容。我个人认为数值目的应该更为重要。如果需要排序/散列,则可以编写一个以Vector为成员的类,并以自己喜欢的方式在该类中定义散列和相等性。


1
我同意数值目的更为重要。但是我们已经有了==and !=运算符。以我的经验,该Equals方法几乎只用于非数值算法。
CodesInChaos
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.