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

时间:2011-01-24 21:02:22

标签: c# nunit floating-point

我正在运行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中有内置的方法吗?

9 个答案:

答案 0 :(得分:15)

来自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显然会导致它通过,因为公差更大。

答案 1 :(得分:8)

这就是我为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;
    }
}

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

答案 2 :(得分:5)

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

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

答案 3 :(得分:3)

我不知道是否有内置的方法来使用nunit,但我建议将每个浮点数乘以你所寻求的精度的10倍,将结果存储为long,并将两个longs比较为彼此。
例如:

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
}

答案 4 :(得分:1)

Assert.That(x, Is.EqualTo(y).Within(10).Percent);

是一个不错的选择(将其更改为相对比较,其中x必须在y的10%之内)。您可能需要为0添加额外的处理,否则在这种情况下将获得准确的比较。

答案 5 :(得分:0)

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

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

答案 6 :(得分:0)

怎么样:

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

答案 7 :(得分:0)

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

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

答案 8 :(得分:-1)

打开FsUnit

实际|>应该(等于errorMargin)