4

所以我使用 CUnit 进行单元测试。我期待类似的东西

float x;
x = atof("17.99");

我想用一个断言来测试它;显然,我可以用一些小ε

CU_ASSERT(abs(x - atof("17.99")) < epsilon);

不过我正在考虑

r = atof("17.99");
CU_ASSERT(0 == memcmp(&x, &r, sizeof(float));

这似乎确实有效。我希望使用它来避免必须根据值在每个测试上手动设置 epsilon。在上述情况下 1e-6 应该足够了;但是,如果值为 1e-10,使用 1e-6 的 epsilon 可能不会出现问题。开发人员必须做出的选择越多,出错的空间就越大。

我的问题是:这种技术在 posix 系统上应该稳定吗?也就是说,如果被比较的两个浮点数是由完全相同的步骤生成的,那么它们的内部表示应该完全相同。

编辑:更重要的是,我最终想要一个 CU_ASSERT_FLOAT_EQUAL 宏。

4

5 回答 5

6

比较浮点值很难。混入字符串不会让事情变得更好,当然也不会像 epsilon 那样引入少量的回旋余地。

看看这篇文章:比较浮点数,2012 版。对于我的钱,ULP 是要走的路。有一些令人讨厌的边缘情况,但您可能可以忽略其中的大部分。

编辑:5 月 31 日 对此进行了更多考虑,我认为您要问的是“如果在测试函数和被测函数中使用完全相同的步骤来计算浮点数,那么答案是否完全相同 -这样我就不用担心 +/- 一些小错误了?”

答案是肯定的,它们将是相同的。但是在这种情况下,您使单元测试变得毫无意义,因为如果被测代码中存在错误,那么测试函数中必然会出现相同的错误。

顺便说一句,不要因为 memcmp(&x, &y, sizeof(x)) 的使用而分心。这只是测试在两个值中设置了完全相同的位。如果这是真的,那么 x == y 必然是真的。坚持 x == y。

于 2012-05-30T16:08:21.767 回答
2

请注意将浮点数与双精度数进行比较,就像在最初的问题中所做的那样。浮点数必然会丢失精度,因此当与全精度双精度数进行比较时,您可能会发现数字不相等,即使计算很完美。

有关详细信息,请参阅http://randomascii.wordpress.com/2012/06/26/doubles-are-not-floats-so-dont-compare-them/

于 2012-07-01T17:20:48.460 回答
1
float r, x;

r = atof("17.99");
CU_ASSERT(0 == memcmp(&x, &r, sizeof(float));

应该具有相同的效果

r = atof("17.99");
CU_ASSERT(x == r);

注意 atof 返回一个双精度,所以

CU_ASSERT(x == atof("17.99"));

不同,但是

CU_ASSERT(x == (float)atof("17.99"));

也应该是一样的。

另请注意,当使用从 x87 继承的指令时,gcc 优化器在 x86 上存在一个长期存在的错误(如果我没记错的话,x86_64 上不会发生这种情况)。

于 2012-05-29T14:09:56.300 回答
1

所以答案似乎是否定的。

原因是即使您在计算每个变量时使用相同的一系列计算,如果在计算值的步骤之间有任何代码,您可能会导致不同的舍入错误,正如@AProgrammer 的响应中的注释所指出的那样。

问题是,虽然您可以声明一个 n 位浮点,但它可能存储在一个更大的寄存器中(x87 使用 80 位寄存器)。如果将该值从寄存器推入内存以释放寄存器以进行其他操作,则该值将被截断(四舍五入?我的笔记去哪儿了......)。当值被带回寄存器时,丢失的精度会进行其余的计算。

另一方面,另一段代码在计算值时可能会经历完全相同的步骤。但是,如果该值没有从寄存器中推出(或在不同的地方推出......),那么当它再次存储在内存中时,您会得到不同的截断。

所有这些都是 IEEE 根据 gcc 邮件列表/错误报告中的注释批准的。

由于自 80386 以来我没有接触过寄存器,我只能猜测现代 x86 和 amd_64 处理器有什么;但我猜测没有提示 gcc 对于 x86 它使用基本的 x87 寄存器集或基本的 SSE 寄存器集。

所以使用 fabs(xy) < epsilon; 的经验法则 持有,对于 CUnit,它以双重格式提供(如果一个人想像我习惯的那样对事物保持警惕,可以轻松编写宏的浮点版本),正如@Martin Beckett 评论的帖子所指出的那样在。

于 2012-06-06T15:27:23.057 回答
0

我认为有一种不同的方式来处理这个问题。问题是关于单元测试。单元测试应该测试和记录被测单元(uut)。

为此,在编写测试时应该询问 uut 可接受的容差是多少。(这可能需要为每个 uut 而不是整个测试项目考虑)。

通过这种方式,测试可以避免测试相等性,测试值是否在可接受的范围内,并记录单元测试结果的可接受容差。

于 2020-01-22T10:20:07.017 回答