首页 文章

根据给定的精度评估两个双精度是否相等,而不是在一定的固定公差范围内

提问于
浏览
26

我正在运行NUnit测试来评估一些已知的测试数据和计算结果 . 这些数字是浮点数的双倍数,所以我不指望它们完全相等,但我不确定如何将它们视为对于给定的精度相等 .

在NUnit中,我们可以与固定的容差进行比较:

double expected = 0.389842845321551d;
double actual   = 0.38984284532155145d; // really comes from a data import
Expect(actual, EqualTo(expected).Within(0.000000000000001));

并且对于零以下的数字工作正常,但随着数字的增加,公差确实需要改变,因此我们始终关注相同数量的精度数字 .

具体来说,此测试失败:

double expected = 1.95346834136148d;
double actual   = 1.9534683413614817d; // really comes from a data import
Expect(actual, EqualTo(expected).Within(0.000000000000001));

当然,更大的数字会因容忍而失败 .

double expected = 1632.4587642911599d;
double actual   = 1632.4587642911633d; // really comes from a data import
Expect(actual, EqualTo(expected).Within(0.000000000000001));

评估两个浮点数的正确方法是什么,它与给定的精度相等?有没有内置的方法在NUnit中执行此操作?

7 回答

  • 14

    来自msdn:

    默认情况下,Double值包含15个十进制数字的精度,但内部最多保留17位数 .

    那么我们假设15岁 .

    所以,我们可以说我们希望容差达到相同的程度 .

    小数点后我们有多少精确数字?我们需要知道小数点上最高有效位的距离,对吧?幅度 . 我们可以使用Log10来实现这一点 .

    然后我们需要将1乘以10 ^精度来得到一个我们想要的精度值 .

    现在,你需要做比我更多的测试用例,但这似乎有效:

    double expected = 1632.4587642911599d;
      double actual = 1632.4587642911633d; // really comes from a data import
    
      // Log10(100) = 2, so to get the manitude we add 1.
      int magnitude = 1 + (expected == 0.0 ? -1 : Convert.ToInt32(Math.Floor(Math.Log10(expected))));
      int precision = 15 - magnitude ;
    
      double tolerance = 1.0 / Math.Pow(10, precision);
    
      Assert.That(actual, Is.EqualTo(expected).Within(tolerance));
    

    已经很晚了 - 这里可能会有问题 . 我根据你的三组测试数据测试了它,每一组都通过了 . 将 pricision 更改为 16 - magnitude 会导致测试失败 . 将其设置为 14 - magnitude 显然会导致它通过,因为公差更大 .

  • 5

    这就是我为The Floating-Point Guide提出的(Java代码,但应该很容易翻译,并附带一个你真正需要的测试套件):

    public static boolean nearlyEqual(float a, float b, float epsilon)
    {
        final float absA = Math.abs(a);
        final float absB = Math.abs(b);
        final float diff = Math.abs(a - b);
    
        if (a * b == 0) { // a or b or both are zero
            // relative error is not meaningful here
            return diff < (epsilon * epsilon);
        } else { // use relative error
            return diff / (absA + absB) < epsilon;
        }
    }
    

    真正棘手的问题是当要比较的数字之一为零时该怎么办 . 最好的答案可能是这种比较应该始终考虑被比较的数字的域意义而不是试图普遍 .

  • 8

    如何将每个项目转换为字符串并比较字符串?

    string test1 = String.Format("{0:0.0##}", expected);
    string test2 = String.Format("{0:0.0##}", actual);
    Assert.AreEqual(test1, test2);
    
  • 0

    我不是用nunit做内置的方法,但我建议将每个浮点数乘以你所寻求的精度的10倍,将结果存储为长数,并将两个长数相互比较 .
    例如:

    double expected = 1632.4587642911599d;
    double actual   = 1632.4587642911633d;
    //for a precision of 4
    long lActual = (long) 10000 * actual;
    long lExpected = (long) 10000 * expected;
    
    if(lActual == lExpected) {  // Do comparison
       // Perform desired actions
    }
    
  • 0

    怎么样:

    const double significantFigures = 10;
    Assert.AreEqual(Actual / Expected, 1.0, 1.0 / Math.Pow(10, significantFigures));
    
  • 3

    这是一个快速的想法,但如何将它们向下移动直到它们低于零?应该像 num/(10^ceil(log10(num))) . . . 不确定它的效果如何,但它是一个想法 .

    1632.4587642911599 / (10^ceil(log10(1632.4587642911599))) = 0.16324587642911599
    
  • 0

    两个值之间的差异应小于任一值除以精度 .

    Assert.Less(Math.Abs(firstValue - secondValue), firstValue / Math.Pow(10, precision));
    

相关问题