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

Sam*_*eff 30 c# floating-point nunit

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

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

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

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

具体来说,此测试失败:

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

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

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

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

Bre*_*ett 17

来自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));
Run Code Online (Sandbox Code Playgroud)

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


Mic*_*rdt 9

这就是我提出的浮点指南(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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • @Samuel:epsilon是相对误差范围,即epsilon为0.01意味着2个值之间的差值必须小于约1%.但是如果一个值为0,这就变得毫无意义,所以在这种情况下我要求另一个值小于epsilon平方.这很随意; 因此,可能不存在普遍有用的比较函数的结论. (2认同)

And*_*ent 7

Assert.That(x, Is.EqualTo(y).Within(10).Percent);
Run Code Online (Sandbox Code Playgroud)

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

更新:另一个不错的选择是

Assert.That(x, Is.EqualTo(y).Within(1).Ulps);
Run Code Online (Sandbox Code Playgroud)

其中Ulps表示units in the last place. 请参阅https://docs.nunit.org/articles/nunit/writing-tests/constraints/EqualConstraint.html#comparing-floating-point-values


Joh*_* K. 6

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

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