经验法则来测试C#中两个双打的相等性?

假设我有一些代码可以执行一些浮点运算并将值存储在双精度数中。 由于某些值无法以二进制forms完美表示,如何在合理程度的确定性下测试相等性?

我如何确定“合理”是什么意思?

可以double.Epsilon以某种方式使用?


更新

几件事。 正如@ ho1指出的那样,double.Epsilon的文档指出,当比较两个双精度表示相等时,你可能想要一个远大于epsilon的值。 以下是文档中的相关段落:

由于其最低有效位数的差异,两个明显等效的浮点数可能无法相等。 例如,C#表达式(double)1/3 ==(double)0.33333,不比较相等,因为左侧的除法运算具有最大精度,而右侧的常量仅精确到指定的数字。 如果创建一个自定义算法来确定是否可以将两个浮点数视为相等,则必须使用大于Epsilon常量的值来确定可接受的两个值的绝对差值,以使两个值相等。 (通常,差异的差异比Epsilon大很多倍 。) – http://msdn.microsoft.com/en-us/library/system.double.epsilon.aspx

……但问题是,要多少倍?

如果它会影响你的答案,我的特殊情况涉及几何计算(例如使用点和向量的点积和交叉积)。 在某些情况下,根据A == BA > BA < B得出不同的结论,所以我正在寻找一个如何确定等价窗口大小的好经验法则。

使用double.Epsilon不一定有效。 double.Epsilon给出大于零的最小可表示值。 但是,由于浮点数的实现方式,它们的精度越低,它们越远离零,因此检查double.Epsilon的差异可能会因两个非常接近的double.Epsilon而失败。

详细信息:base-2浮点数表示为有效数字 – 1到2之间的数字 – 乘以2加到某个指数。 double对于有效数的小数部分有52位,对指数有11位精度。 如果指数是一个非常大的负值并且有效数为0,那么你得到的值接近于double.Epsilon ,但是如果你的指数足够大,那么即使两个有效数值的非常小的差值也会产生很大的值大于double.Epsilon

有关如何测试两个浮点数的相等性的完整讨论,请参阅Bruce Dawson的“比较浮点数,2012版” 。 总而言之,有三种主要的比较方法:

使用绝对差异

正如Joel Coehoorn的例子 ,但要非常小心地选择一个适当大小的值,这与Joel的例子不同。

使用相对差异

类似于以下内容:

 if (Math.Abs(a - b) / b <= maxRelativeError) { return true; } 

但是,有并发症; 你应该除以两个值中较大的一个,并且这个函数对于接近于零的值表现不佳,除非你还添加一个最大绝对差值的检查。 有关详细信息,请参阅论文

使用最后一个单位

使用最后位置单位(ULP)进行比较意味着检查有效位数的最后部分。 (本文将其称为“使用整数进行比较。”)这是一种更复杂的方法,但非常强大。 本文提供了C语言的源代码; 对于C#,你可以使用BitConverter.DoubleToInt64Bits 。

回应你的编辑

“多少倍?” 这实际上是您的应用程序域的问题,这可能是.NET Framework不提供默认方法的原因,但我很幸运使用ULP比较,最大ULP差异为4。

这取决于你正在使用什么样的价值观。 如果您正在处理只关注2个小数点的数字,那么可以使用0.001。 你有时可能会使用Epsilon ,但通常我认为不是。

编辑:删除了对货币的引用,因为它分散了注意力。

从MSDN引用:

如果创建一个自定义算法来确定是否可以将两个浮点数视为相等,则必须使用大于Epsilon常量的值来确定可接受的两个值的绝对差值,以使两个值相等。 (通常,差异幅度比Epsilon大很多倍。)

 double d1 = GetRandomDouble(); double d2 = GetRandomDouble(); if (Math.Abs(d1 - d2) < double.Epsilon) { // the doubles are equal } 

请注意,在实践中,此代码仅相当于d1 == d2 ,因为epsilon被定义为最小可能的正值> 0.因此,您永远不会有0到epsilon之间的值,如果您有这种舍入/导致问题的精度错误,你也会在这里看到==运算符。

但你可以做的是使用这种技术来定义你自己的精度水平 - 你自己的epsilon。 我希望double.Equals()有一个重载的技术,但文档很明显,一个奇怪的不存在。 所以让我们自己做:

 public static bool IsEqual(this double d1, double d2, unsigned int precisionFactor) { return Math.Abs(d1 - d2) < precisionFactor * double.Epsilon; } 

所以我正在寻找一个很好的经验法则来确定如何确定等价窗口的大小。

不幸的是,这里没有好的经验法则。 这完全取决于您的计划的需求。 玩具物理模拟可能更喜欢非常高的epsilon,因此不会错过碰撞。 同时,统计软件包希望低epsilon更准确。 您只需根据应用需求进行调整即可。

The question is, but many times greater??

多大程度取决于输入和您执行的操作数量。 除了数量级考虑之外,每个操作都会增加舍入误差。 如果你在比较它们之前对数字做了很多计算,那么它更多的是有效数字而不是机器精度。

您的窗口需要大于因完成任何计算而累积的最坏情况舍入误差。 如果它更小,则可能存在比较失败的杂散情况。

下面是Bruce Dawsone的“比较浮点数”中提到的实现(比较IEEE表示的最后几位),移植到C#。

有人可能会说Float.NaN != Float.NaN 。 此代码将所有特殊IEEE浮点值(NaN,Inf等)视为相等。 对于unit testing,这可能是你想要的。 对于真正的生产代码,您可能不应该将NaN或Inf与任何东西进行比较 – 您应该抛出exception或智能的东西。

这与Google Test(gtest)用于C ++的技术相同。

Gtest对maxUlps使用默认值4

  public static bool AlmostEqual2sComplement(float A, float B, int maxUlps) { // Make sure maxUlps is non-negative and small enough that the // default NAN won't compare as equal to anything. if (!(maxUlps > 0 && maxUlps < 4 * 1024 * 1024)) throw new Exception("maxUlps is invalid"); Int32 aInt = BitConverter.ToInt32(BitConverter.GetBytes(A),0); // Make aInt lexicographically ordered as a twos-complement int if (aInt < 0) aInt = Int32.MinValue + (-aInt); // Make bInt lexicographically ordered as a twos-complement int Int32 bInt = BitConverter.ToInt32(BitConverter.GetBytes(B), 0); if (bInt < 0) bInt = Int32.MinValue + (-bInt); Int64 intDiff = Math.Abs(aInt - bInt); if (intDiff <= maxUlps) return true; return false; } public static bool AlmostEqual2sComplement(double A, double B, int maxUlps) { // Make sure maxUlps is non-negative and small enough that the // default NAN won't compare as equal to anything. if (!(maxUlps > 0 && maxUlps < 4 * 1024 * 1024)) throw new Exception("maxUlps is invalid"); Int64 aInt = BitConverter.ToInt64(BitConverter.GetBytes(A), 0); // Make aInt lexicographically ordered as a twos-complement int if (aInt < 0) aInt = Int64.MinValue + (- aInt); // Make bInt lexicographically ordered as a twos-complement int Int64 bInt = BitConverter.ToInt64(BitConverter.GetBytes(B), 0); if (bInt < 0) bInt = Int64.MinValue + (- bInt); Int64 intDiff = Math.Abs(aInt - bInt); if (intDiff <= maxUlps) return true; return false; }