代码之家  ›  专栏  ›  技术社区  ›  devuxer

检验C中两个双精度数相等性的经验法则

  •  14
  • devuxer  · 技术社区  · 15 年前

    假设我有一些代码可以执行浮点运算并以双精度形式存储值。因为有些值不能用二进制完美地表示,我如何在合理的确定度下测试相等性呢?

    如何确定“合理”的含义?

    罐头 double.Epsilon 以某种方式使用?


    更新

    夫妻两件事。正如@ho1所指出的,文件 双ε 指出,当为了相等而比较两个双精度数时,你可能想要一个比epsilon大得多的值。以下是文档中的相关段落:

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

    …但问题是,比这大多少倍??

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

    6 回复  |  直到 9 年前
        1
  •  18
  •   Community Mohan Dere    8 年前

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

    细节:一个以2为基数的浮点数表示为一个有效位—一个介于1和2之间的数字—乘以2,得到某个指数。一个双精度数的有效位的小数部分有52位,指数的精度有11位。如果指数是一个非常大的负值,有效位为0,则得到的值接近 双ε ,但是如果指数足够大,那么即使两个有意义的值之间有非常小的差异,也会导致一个值比 双ε .

    有关如何测试两个浮点数是否相等的完整讨论,请参见 "Comparing Floating Point Numbers, 2012 Edition" 布鲁斯·道森。总的来说,有三种主要的比较方法:

    使用绝对差异

    如在 Joel Coehoorn's example 但是 非常 注意选择一个适当大小的值,不像Joel的例子。

    使用相对差异

    如下所示:

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

    但是,也有一些复杂的情况;您应该除以两个值中较大的一个,对于接近零的值,这个函数的性能很差,除非您还添加了一个最大绝对差的检查。详情请参阅本文。

    使用末位单位

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

    响应您的编辑

    “多大几倍?”这确实是您的应用程序域的一个问题,这可能就是.NET框架不提供默认方法的原因,但我很幸运地使用了ULPS比较,最大ULPS差异为4。

        2
  •  2
  •   Hans Olsson    15 年前

    这在一定程度上取决于你所使用的价值观。如果你使用的是只关心2个小数点的数字,那么使用0.001就可以了。你可能会使用 Epsilon 有时,但通常我不这么认为。

    编辑:删除对货币的引用,因为它偏离了重点。

    来自msdn的报价:

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

        3
  •  2
  •   Joel Coehoorn    15 年前
    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() 为这项技术超载,但是 documentation 很明显,一个奇怪的不存在。那么让我们来做我们自己的:

    public static bool IsEqual(this double d1, double d2, unsigned int precisionFactor)
    {
       return Math.Abs(d1 - d2) < precisionFactor * double.Epsilon;
    }
    
        4
  •  1
  •   munificent    15 年前

    因此,我正在寻找一个好的经验法则来确定等价窗口的大小。

    不幸的是,这里没有好的经验法则。这完全取决于你的计划的需要。玩具物理模拟可能更喜欢非常高的epsilon,这样碰撞就不会错过。同时,一个统计数据包会希望低epsilon更准确。你只需要根据你的应用程序的需要调整它。

        5
  •  1
  •   Josh Sterling    15 年前

    The question is, but many times greater??

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

    您的窗口需要大于任何计算结果累积的最坏情况舍入误差。如果比较小,可能会出现比较失败的情况。

        6
  •  1
  •   Mark Lakata    10 年前

    以下是中提到的实现(比较IEEE表示的最后一位) "Comparing Floating Point Numbers", by Bruce Dawsone ,移植到C。

    有人会说 Float.NaN != 弗拉特南 . 此代码将把所有特殊的IEEE浮点值(NAN、INF等)视为相等。对于单元测试,这可能是您想要的。对于真正的生产代码,您可能不应该将NAN或INF与任何东西进行比较——您应该抛出一个异常或一些聪明的东西。

    这与 Google Test (gtest) for C++ as well .

    gtest使用默认值 对于 maxUlps .

        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;
        }