【问题标题】:Why does adding double.epsilon to a value result in the same value, perfectly equal?为什么将 double.epsilon 添加到一个值会产生相同的值,完全相等?
【发布时间】:2015-02-14 20:57:21
【问题描述】:

我有一个单元测试,测试边界:

[TestMethod]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void CreateExtent_InvalidTop_ShouldThrowArgumentOutOfRangeException()
{
    var invalidTop = 90.0 + Double.Epsilon;
    new Extent(invalidTop, 0.0, 0.0, 0.0);
}

public static readonly double MAX_LAT = 90.0;

public Extent(double top, double right, double bottom, double left)
{
    if (top > GeoConstants.MAX_LAT)
        throw new ArgumentOutOfRangeException("top"); // not hit
}

我以为我会通过向其添加最小可能的正双精度来将 90.0 倾斜到边缘,但现在没有抛出异常,知道为什么吗?

调试时,我看到 top 是 90,而它应该是 90.00000000.... 什么。

编辑: 我应该更努力地思考一下,90+Double.Epsilon 会失去它的分辨率。似乎最好的方法是做一些位移。

解决方案:

[TestMethod]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void CreateExtent_InvalidTop_ShouldThrowArgumentOutOfRangeException()
{
    var invalidTop = Utility.IncrementTiny(90); // 90.000000000000014
    // var sameAsEpsilon = Utility.IncrementTiny(0);
    new Extent(invalidTop, 0, 0, 0);
}

/// <summary>
/// Increment a double-precision number by the smallest amount possible
/// </summary>
/// <param name="number">double-precision number</param>
/// <returns>incremented number</returns>
public static double IncrementTiny(double number)
{
    #region SANITY CHECKS
    if (Double.IsNaN(number) || Double.IsInfinity(number))
        throw new ArgumentOutOfRangeException("number");
    #endregion

    var bits = BitConverter.DoubleToInt64Bits(number);

    // if negative then go opposite way
    if (number > 0)
        return BitConverter.Int64BitsToDouble(bits + 1);
    else if (number < 0)
        return BitConverter.Int64BitsToDouble(bits - 1);
    else
        return Double.Epsilon;
}

/// <summary>
/// Decrement a double-precision number by the smallest amount possible
/// </summary>
/// <param name="number">double-precision number</param>
/// <returns>decremented number</returns>
public static double DecrementTiny(double number)
{
    #region SANITY CHECKS
    if (Double.IsNaN(number) || Double.IsInfinity(number))
        throw new ArgumentOutOfRangeException("number");
    #endregion

    var bits = BitConverter.DoubleToInt64Bits(number);

    // if negative then go opposite way
    if (number > 0)
        return BitConverter.Int64BitsToDouble(bits - 1);
    else if (number < 0)
        return BitConverter.Int64BitsToDouble(bits + 1);
    else
        return 0 - Double.Epsilon;
}

这样就可以了。

【问题讨论】:

  • 双精度是一件令人讨厌的事情,但是当比较 A 和 B 之间的最大偏差时,Double.Epsilon,所以你可能没有以非常非常小的幅度小费。
  • 这里有一篇有趣的文章:johndcook.com/blog/2012/01/05/double-epsilon-dbl_epsilon TL;DR 是“Double.Epsilon 没有你想象的那么有用!”
  • 布鲁斯·道森有a great series of articles on ULPs and comparing floats and doubles。他的示例代码往往是 C++,但文章大多是解释。
  • 这对于您的目的可能很好(尽管如果您想对负值进行等效测试,我不确定它是否正常工作),但将其用于其他目的的人应该考虑:此函数在呈现无穷大、nan、最大值、零、非规范化或负数时表现合理,如果不是,你关心吗?

标签: c# unit-testing double double-precision epsilon


【解决方案1】:

the documentation of Double.Epsilon:

Epsilon 属性的值反映了最小的正数 Double 在数值运算或比较中重要的值 Double 实例的值为零时

(强调我的。)

将它添加到 90.0 不会产生“90.0 之后的下一个最小值”,这只会再次产生 90.0。

【讨论】:

【解决方案2】:

因为 Double.Epsilon 是双精度数中“最小的显着变化”(松散地说)。

..但是这并不代表你使用它会有任何效果。

如您所知,浮点数/双精度数的分辨率取决于它们所包含的 vlue 的大小。比如人工:

  • ...
  • -100 -> +-0.1
  • -10 -> +-0.01
  • 0 -> +-0.001
  • 10 -> +-0.01
  • 100 -> +-0.1
  • ...

如果分辨率是这样的,Epsilon 将是0.001,因为它是最小的可能变化。但是在这样的系统中1000000 + 0.001 的预期结果是什么?

【讨论】:

  • "Double.Epsilon 是双精度数中最小的显着变化" 不完全正确 - 它是可以表示为 double 的最小双精度大于 0。没有可以从 1.0 中减去的 double 值,这将导致答案等于 double.Epsilon
  • @DStanley:谢谢,对此进行了澄清。我从来没有打算解释这部分,只是想让读者对这个话题有所了解。
【解决方案3】:

Double.Epsilon 是可表示的最小正值。仅仅因为它本身是可表示的,并不意味着它是任何其他可表示值和下一个最高值之间的最小值。

想象一下,您有一个只表示整数的系统。您可以将任意整数表示为 5 位有效数字,以及一个刻度(例如,在 1-100 范围内)。

所以这些值是可以精确表示的,例如

  • 12345(数字=12345,比例= 0)
  • 12345000(数字=12345,比例= 3)

在该系统中,“epsilon”值将是 1...但如果将 1 加到 12345000 上,您仍然会得到 12345000,因为系统无法表示 12345001 的确切结果。

现在对 double 应用相同的逻辑,包括其所有复杂性,你会得到一个小得多的 epsilon,但同样的一般原则:一个不同于零的值,但最终仍然不会产生任何影响添加到更大的数字。

请注意,更大的值也具有相同的属性 - 例如,如果x 是一个非常大的double,那么x + 1 很可能等于x,因为两个“相邻”之间的差距加倍随着值变大,变为大于 2。

【讨论】:

  • @BrainSlugs83:你在哪里看到 double.Epsilon == 0d 返回 true?它不适合我。
  • 你是对的! -- 我发誓一分钟前在 Console.WriteLine 测试中这样做了,但现在绝对不是现在 -- 编辑该评论为时已晚,所以我删除了它 -- 不想混淆任何人。现在仔细检查IEE754,让我的头脑清醒。
【解决方案4】:

在 C99 和 C++ 中,执行您尝试执行的操作的函数称为 nextafter,位于 math.h 中。我不知道 C# 是否有任何等价物,但如果有,我希望它有类似的名称。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-07
    • 1970-01-01
    • 2016-03-29
    • 1970-01-01
    相关资源
    最近更新 更多