Gtest在零附近的C++浮点比较失败

C++ Float Comparison Around Zero Fails With Gtest

本文关键字:比较 失败 C++ Gtest      更新时间:2023-10-16

我们在我的一个单元测试中挣扎了很长一段时间。在调查过程中,我们发现了根本原因,这似乎是浮动中的比较(请参阅下面的代码片段,我简化了计算,但它仍然失败)。

TEST_F( MyFloatTest, thisOneDoesFail)
{
const float toCompare = 0.2f - 1.0f + 0.9f;
EXPECT_FLOAT_EQ( toCompare, 0.1f );
}

结果是:

实际值:0.1应为:to比较即:0.099999964

有一些数字数学背景,我们仍然不知道为什么这个测试失败,而使用std::numerical_limits:epsilon的自定义浮点比较通过了。所以在某个时候,我们开始认为GTest是错误的,我们对它进行了调试。它使用了奇怪的表达式,我们没有完全理解。更奇怪的是:下面的测试通过了,尽管我只添加了一个1:

TEST_F( MyFloatTest, thisOnePasses)
{
const float toCompare = 1.2f - 1.0f + 0.9f;
EXPECT_FLOAT_EQ( toCompare, 1.1f );
}

我们认为在包含负浮点值时可能会出现一些问题,但下一个测试也通过了:

TEST_F( MyFloatTest, thisOnePassesAlso)
{
const float toCompare = 0.2f - 1.0f + 1.9f;
EXPECT_FLOAT_EQ( toCompare, 1.1f );
}

因此,对我们来说,Gtest的EXPECT_FLOAT_EQ宏似乎只是在零附近存在问题。有人知道这种行为吗?你在你的环境中见过类似的情况吗?(顺便说一句:我们使用的是MSVC2015)它只是因为GTest中提到的4 ULP精度而意外失败吗?(我们也不完全清楚)。

问题是,具有较小值和较大中间值的浮点和往往会有较大的相对误差。您可以通过编写来减少错误

const float toCompare = 0.2f - (1.0f - 0.9f);

在原始代码中,最大的中间值是0.2-1.0=-0.8,是最终结果的八倍。更改代码后,最大中间值为0.1,等于最终结果。如果你检查通过的示例测试,在每种情况下,你都没有比最终结果更大的中间结果。

问题不在于EXPECT_FLOAT_EQ宏,而在于计算。

它只是由于GTest中提到的4 ULP精度而意外失败吗?

在我看来就是这样。

尝试以下(非常粗糙,不可移植!)测试代码:

float toCompare = 0.2f - 1.0f + 0.9f;
int i = *reinterpret_cast<int*>(&toCompare);
std::cout << i << 'n';
float expected = 0.1f;
i = *reinterpret_cast<int*>(&expected);
std::cout << i << 'n';

在我的系统上,输出是:

1036831944
1036831949

尾数正好相隔5个ULP。4 ULP的比较对于计算的误差是不够的。

0.2f - 1.0f很好,在我的系统上根本没有精度错误。剩下的是-0.8f + 0.9f。这就是错误的来源(在我的系统上)。我不是一个足够的专家来告诉你为什么这个计算有5 ULP精度误差。


在预期出现一定程度错误的情况下,请使用EXPECT_NEAR

在我看来,问题是你假设0.2f-1.0f+0.9f等于0.1f。0.2 0.9 0.1中没有一个可以精确地表示为浮点(或双精度或任何其他二进制浮点表示)。

0.2f和0.9f实际上是0.2和0.9的近似值,几乎没有理由假设你的和会给出与0.1f相同的0.1近似值。虽然0.2f和0.9的相对误差大致相同,但由于抵消,和的相对误差可能会大得多。

如果你对可以精确表示为浮点的数字做同样的事情,例如0.25f-1.0f+0.875f,你会发现这等于0.125f