【问题标题】:comparing floating point numbers in C for unit testing比较 C 中的浮点数以进行单元测试
【发布时间】:2012-06-03 17:52:38
【问题描述】:

所以我使用 CUnit 进行单元测试。我期待像

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

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

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 宏。

【问题讨论】:

  • 是的,如果它们是由相同的步骤生成的,它们的表示将是相同的。除非结果是 NaN,否则您将拥有 x == y
  • memcmp() 解决了哪些简单的== 无法解决的问题?
  • @DanielFischer:可以保证吗?我认为是,但是潜在的编译器优化(宽浮点寄存器中的中间结果等)呢?
  • @mouviciel:对于浮点类型,使用 == 不起作用,即使具有相同的表示。见dl.acm.org/citation.cfm?id=103163
  • @MichaelConlen 有了同样的表示,你肯定会得到== 是真的,除非你和NaN 混在一起。

标签: c floating-point arithmetic-expressions


【解决方案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"));

应该也是一样的。

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

【讨论】:

    【解决方案2】:

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

    看看这篇文章:Comparing Floating Point Numbers, 2012 Edition。对于我的钱,ULP 是要走的路。有一些令人讨厌的边缘情况,但您可能可以忽略其中的大部分。

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

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

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

    【讨论】:

      【解决方案3】:

      所以答案似乎是否定的。

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

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

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

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

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

      所以使用 fabs(x-y)

      【讨论】:

      • > 由完全相同的步骤生成最终执行起来非常棘手,并且问题比您列出的问题多。如果这两次运行是在不同的架构上(甚至是 x86 与 x64)或不同的编译器或不同的构建设置或......有关挑战的完整讨论,请参阅我写的这篇文章:randomascii.wordpress.com/2013/07/16/floating-point-determinism
      【解决方案4】:

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

      详情请见http://randomascii.wordpress.com/2012/06/26/doubles-are-not-floats-so-dont-compare-them/

      【讨论】:

        【解决方案5】:

        我认为解决这个问题有不同的方法。 问题是关于单元测试。单元测试应该测试和记录被测单元(uut)。

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-02-01
          • 2022-12-14
          • 1970-01-01
          • 2015-12-25
          • 2021-12-07
          • 2011-10-23
          • 1970-01-01
          相关资源
          最近更新 更多