比较C中的浮点数进行单元测试

时间:2012-05-29 13:37:49

标签: c floating-point arithmetic-expressions

所以我使用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-6的epsilon值为1e-10可能不会出现问题。开发人员拥有的选择越多,就越容易出错。

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

编辑:更重要的是,我最终会喜欢CU_ASSERT_FLOAT_EQUAL宏。

5 个答案:

答案 0 :(得分:6)

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

看一下这篇文章:Comparing Floating Point Numbers, 2012 Edition。对于我的钱,ULP是要走的路。有一些令人讨厌的边缘情况,但你可能会忽略它们中的大多数。

编辑:5月31日 考虑到这一点之后,我想你要问的是“如果在测试函数和被测函数中使用完全相同的步骤计算浮点数,答案是否完全相同 - 这样我就不会不用担心+/-一些小错误?“

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

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

答案 1 :(得分:2)

注意将float与double进行比较,如初始问题中所做的那样。浮点数必然会失去精度,因此当它与全精度双精度数据进行比较时,您可能会发现数字不相等,即使计算结果是完美的。

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

答案 2 :(得分: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返回一个double,所以

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

不同,但

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

也应该是一样的。

另请注意,gcc优化器有一个long standing bug with this on x86 when using the instructions inherited from the x87(如果我没弄错的话,x86_64上不会发生这种情况)。

答案 3 :(得分:1)

所以答案似乎是否定的。

原因是,即使您在计算每个变量时使用相同的一系列计算,如果在计算值的步骤之间存在任何代码,您可能会导致不同的舍入误差,如{{3}中的注释所示}。

问题在于,虽然您可能声明一个n位浮点,但它可能存储在一个更大的寄存器中(x87使用一个80位寄存器)。如果将值从寄存器中推出并进入内存以释放寄存器以进行其他操作,那么该值将被截断(舍入?我的笔记在哪里......)。  当值返回到寄存器时,精度损失会进行剩余的计算。

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

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

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

因此使用fabs的经验法则(x-y)&lt;小量;暂停,对于CUnit来说,它是以双重格式提供的(如果一个人想要像我有习惯那样肛门,可以很容易地写一个浮动版本的宏),正如@Martin Beckett所评论的帖子所指出的那样上。

答案 4 :(得分:0)

我认为有另一种方法来解决此问题。 问题是关于单元测试。单元测试应同时测试并记录被测单元(uut)。

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

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