C# 为什么将double.epsilon添加到一个值会导致相同的值,完全相等?

C# 为什么将double.epsilon添加到一个值会导致相同的值,完全相等?,c#,unit-testing,double,double-precision,epsilon,C#,Unit Testing,Double,Double Precision,Epsilon,我有一个单元测试,测试边界: [TestMethod] [ExpectedException(typeof(ArgumentOutOfRangeException))] public void CreateExtent_InvalidTop_ShouldThrowArgumentOutOfRangeException() { var invalidTop = 90.0 + Double.Epsilon; new Extent(invalidTop, 0.0, 0.0, 0.0);

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

[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添加最小可能的正数double来使其超出边缘,但现在没有抛出异常,知道为什么吗

在调试时,我看到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;
}
[TestMethod]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
公共无效CreateExtent\u InvalidTop\u应通过GumentAutoFrangeException()进行
{
var invalidTop=Utility.IncrementTiny(90);/90.000000000000014
//var sameAsEpsilon=Utility.IncrementTiny(0);
新范围(invalidTop,0,0,0);
}
/// 
///以尽可能小的数量增加一个双精度数字
/// 
///双精度数
///递增数
公共静态双增量极小(双倍数字)
{
#区域健全性检查
if(Double.IsNaN(数字)| Double.isfinity(数字))
抛出新ArgumentOutOfRangeException(“数字”);
#端区
var位=位转换器。DoubleToInt64位(数字);
//如果是负数,则相反
如果(数字>0)
返回BitConverter.Int64BitsToDouble(位+1);
否则如果(数字<0)
返回BitConverter.Int64BitsToDouble(位-1);
其他的
返回双ε;
}
/// 
///以尽可能小的量减少双精度数字
/// 
///双精度数
///递减数
公共静态双递减率(双倍数字)
{
#区域健全性检查
if(Double.IsNaN(数字)| Double.isfinity(数字))
抛出新ArgumentOutOfRangeException(“数字”);
#端区
var位=位转换器。DoubleToInt64位(数字);
//如果是负数,则相反
如果(数字>0)
返回BitConverter.Int64BitsToDouble(位-1);
否则如果(数字<0)
返回BitConverter.Int64BitsToDouble(位+1);
其他的
返回0-Double.Epsilon;
}
这就完成了任务。

根据:

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

(我的重点。)


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

因为Double.Epsilon是一个双倍数中“最小的明显变化”(松散地说)

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

正如您所知,浮动/双精度在其分辨率上有所不同,这取决于它们包含的vlue的大小。例如,人工:

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

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

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

假设你有一个只表示整数的系统。您可以将任何整数表示为5位有效数字,并使用刻度(例如,范围为1-100)

例如,这些值是完全可以表示的

  • 12345(数字=12345,刻度=0)
  • 12345000(数字=12345,刻度=3)
在那个系统中,“ε”值是1。。。但是,如果将1添加到12345000中,您仍然会得到12345000,因为系统无法表示12345001的确切结果

现在,将相同的逻辑应用于双精度,以及其所有复杂之处,得到的ε要小得多,但基本原理相同:一个不同于零的值,但当与较大的数字相加时,仍然不会产生任何差异


注意到,较大的值也具有相同的属性,例如,如果<代码> x < /C++ >是非常大的<代码>双< /代码>,则代码> X+1 可能等于 X<代码>,因为两个“相邻”双倍的差距随着值变大而变得大于2。执行您试图执行的操作的函数被调用,并且位于

math.h
中。我不知道C#是否有任何等价物,但如果有,我希望它有一个类似的名称。

双精度是一件令人讨厌的事情,但是当比较a和B之间的最大偏差时,是
Double.Epsilon
,所以你可能没有以非常小的幅度给它足够的倾斜;DR是“
Double.Epsilon
没有你想象的那么有用!”布鲁斯·道森说。他的示例代码往往是C++,但文章大多是解释性的。这可能对您的目的很好(虽然我不确定它是否正确地工作,如果您想做一个负值的等价测试),但是人们为了适应其他目的而应该考虑:这个函数在无穷大的情况下是否合理地运行,maxvalue、零、非规范化或负数,如果不是,你在乎吗?在任何情况下都没有一个数字可以工作
double
的最大精度为15位,因此,如果将一个很小的数字加在1上,结果是“不同的”
double
不能加在1000000上,但仍然会得到一个“不同的”
double
@Muhammad:这是一个独立的问题。参见和,“Double.Epsilon是一个双精度数字中最小的明显变化”不完全正确-它是最小的双精度g