22

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

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

这可以完成工作。

4

4 回答 4

26

根据以下文档Double.Epsilon

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

(强调我的。)

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

于 2014-12-16T14:10:49.610 回答
21

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。

于 2014-12-16T14:13:10.067 回答
2

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

于 2014-12-16T21:03:47.990 回答
1

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

..但这并不意味着当你使用它时它会有任何效果。

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

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

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

于 2014-12-16T14:12:31.363 回答