Double.Epsilon用于相等、大于、小于、小于或等于、大于或等于的比较。

62

http://msdn.microsoft.com/en-us/library/system.double.epsilon.aspx

如果您创建了一个自定义算法来确定两个浮点数是否可以被视为相等,那么您必须使用大于 Epsilon 常量的值来建立可接受的绝对差距范围以使这两个值被视为相等。 (通常,这种差距范围要比 Epsilon 大得多。)

那么这真的不能作为比较中使用的epsilon吗?我不是很理解MSDN的措辞。

它可以用作此处示例中的epsilon吗?- 如何进行float和double比较的最有效方法是什么?

最后,这似乎非常重要,因此我希望确保我具有用于相等、大于、小于、小于或等于以及大于或等于的坚实实现。

9个回答

92

我不知道他们写这句话时在想些什么。 Double.Epsilon 是最小的浮点数,它既不为零也不是非规格化数。所有你知道的是,如果存在截断误差,那么它总是会比这个值要大得多

System.Double类型可以准确地表示多达15位数字的值。因此,如果一个双精度浮点数x等于某个常量,那么一个简单的一阶估计就是使用一个常量的epsilon,即constant * 1E-15。

public static bool AboutEqual(double x, double y) {
    double epsilon = Math.Max(Math.Abs(x), Math.Abs(y)) * 1E-15;
    return Math.Abs(x - y) <= epsilon;
}

你必须要注意,截断误差是会累加的。如果xy都是计算出来的值,那么你需要增加epsilon。


8
Microsoft 表示:表示大于零的最小正 Double 值。请参阅 http://msdn.microsoft.com/zh-cn/library/system.double.epsilon%28v=vs.110%29.aspx。 - david.pfx
1
@Ian,这就是Double.Epsilon应该是的样子,但david.pfx定义的却不是 :-( - Mark Hurd
1
1.0 + 1E-16 = 1.0,还有很多类似的 :) - Hans Passant
5
有经验的程序员说出这样的话,确实可以解释为什么我们会收到那么多关于这个主题的问题。二进制不等于十进制。 - Hans Passant
好的,如果我理解正确的话,对于任何val,在double可表示的数字集合中,val > 0是否总是会给出与val >= double.Epsilon相同的结果? - Flynn1179
显示剩余5条评论

52

我希望确保实现了等于、大于、小于、小于等于和大于等于的可靠方法。

您正在使用二进制浮点算术。

二进制浮点算术是设计用来表示类似长度、质量、电荷、时间等物理量。

因此,您使用二进制浮点算术是为了对物理量进行运算。

物理量的测量总是有特定的精度,取决于用来测量它们的设备的精度。

既然您提供了要操作的数量值,则您知道该数量的“误差范围”。例如,如果您提供的数量是“建筑物高度为123.56米”,那么您知道这是准确到厘米,但不到微米。

因此,在比较两个数量是否相等时,所需的语义是:“这两个数量在每个测量指定的误差范围内是否相等?”

现在我们有了答案。您必须跟踪每个数量的误差;例如,建筑物高度“在123.56米的0.01范围内”,因为您知道这是测量的精度。如果您得到另一个测量值是123.5587,并想知道这两个测量值是否在误差容限内“相等”,则进行减法并查看其是否落在误差容限内。在这种情况下,它确实是相等的。如果测量结果的精度确实为微米,则它们不相等。

简而言之:您是唯一一个知道合理误差容限的人,因为您是唯一一个知道操作数字来自哪里的人。根据您使用的设备的精度,使用任何合适的误差容限来处理您的测量。


2
虽然指出公差是更实用和直接的度量方法肯定是正确的,但我关注的是编码表示的不准确性作为一般的第一遍规则,而公差将是根据具体情况的可选的第二遍。 - ss2k
2
Eric,如果你创建了某种数据结构(例如用于游戏),并希望允许其他游戏使用它,每个人都会有不同的容差,因为他们都使用不同的坐标系统。因此,决定你的 epsilon 将是多少,不仅仅是个人条件的问题,我认为。 - Tom
2
你没有回答这个问题。这不是关于品味或上下文的问题,而是与内存中双精度浮点数(IEEE 754)的定义有关。 - Eric Ouellet
浮点数值经常用于合成计算,这些计算没有基于物理测量技术的误差范围。了解数据类型的精度限制对于许多问题非常重要。 - tukra

12

如果你有两个非常接近1.0的double数值,但它们仅在最不重要的位上存在差异,那么它们之间的差异将比Double.Epsilon多出很多个数量级。事实上,这个差异为324个十进制数量级。这是由指数部分的影响造成的。Double.Epsilon具有巨大的负指数,而1.0的指数为零(当然,在偏差被移除后)。

如果你想比较两个类似的值是否相等,则需要选择适合于这些值数量级的自定义epsilon值。

如果你要比较的double值接近1.0,则最不重要的位的值会接近0.0000000000000001。如果你要比较的double值是万亿级别的,则最不重要的位的值可能高达一千。对于这两种情况,不能使用单个epsilon值进行相等性比较。


8
我刚刚按照肯特·博加特的想法做了这件事。
private bool IsApproximatelyEqual(double x, double y, double acceptableVariance)
{
     double variance = x > y ? x - y : y - x;
     return variance < acceptableVariance;

     //or
     //return Math.Abs(x - y) < acceptableVariance;
}

5

这可以用于比较,假设您想确保两个值完全相等或具有双精度类型的最小可表示差异。一般来说,您需要使用大于double.Epsilon的数字来检查两个双精度数是否大致相等。

.NET框架为什么不定义类似于此的内容?

bool IsApproximatelyEqual(double value, double permittedVariance);

超出了我的能力范围。

4

我使用以下代码

public static class MathUtil {
    /// <summary>
    /// smallest such that 1.0+EpsilonF != 1.0
    /// </summary>
    public const float EpsilonF = 1.192092896e-07F;

    /// <summary>
    /// smallest such that 1.0+EpsilonD != 1.0
    /// </summary>
    public const double EpsilonD = 2.2204460492503131e-016;

    [MethodImpl( MethodImplOptions.AggressiveInlining )]
    public static bool IsZero( this double value ) {
        return value < EpsilonD && value > -EpsilonD;
    }

    [MethodImpl( MethodImplOptions.AggressiveInlining )]
    public static int Sign( this double value ) {
        if ( value < -EpsilonD ) {
            return -1;
        }
        if ( value > EpsilonD )
            return 1;
        return 0;
    }

如果你想检查两个双精度浮点数'a'和'b'是否相等,可以使用

(a-b).IsZero();

如果您想获取比较结果,请使用

(a-b).Sign();

3
通常情况下,Epsilon与两个减数的绝对值中较大的那个进行相乘,因为Epsilon取决于较大的操作数。 - David Gausmann

1

这是一些包含在Silverlight Control Toolkit中两次的代码:

    public static bool AreClose(double value1, double value2)
    {
        //in case they are Infinities (then epsilon check does not work)
        if(value1 == value2) return true;
        // This computes (|value1-value2| / (|value1| + |value2| + 10.0)) < DBL_EPSILON
        double eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * DBL_EPSILON;
        double delta = value1 - value2;
        return(-eps < delta) && (eps > delta);
    }

在某个地方,他们使用1e-6作为epsilon;在另一个地方,他们使用1.192093E-07。你需要选择自己的epsilon。

这个看起来很有前途。看起来他们解决了比较高值时减少小数精度的问题。当然,你必须考虑是否需要这种精度缩放。它与具有静态 epsilon 版本一样适用。 - Ultroman the Tacoman

1

你没有选择,必须自己计算或定义自己的常量。

double calculateMachineEpsilon() {
    double result = 1.0;
    double one = 1.0/256;

    while(one + result/2.0 != 1.0) {
        result/=2.0;
    }
    return result;
}

1
比较双精度浮点数的问题在于,当您比较两个不同的数学结果时,它们相等,但由于舍入误差而不评估为相同的值,它们将有一些差异...这些差异大于 epsilon,除了边缘情况。使用可靠的 epsilon 值也很困难。有些人认为,如果它们之间的差异小于某个百分比值,则两个双精度浮点数相等,因为使用静态最小差异 epsilon 可能意味着当双精度浮点数本身很高或很低时,您的差异太小或太大。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接