比较浮点数 - Google 测试框架

4

在阅读用户@skrebbel在SO上的这篇帖子时,他表示Google测试框架在比较浮点数和双精度浮点数方面做得非常好且快速。因此,我编写了以下代码来检查代码的有效性,但显然我漏掉了什么,因为我本来期望进入“几乎相等”的部分,这是我的代码:

float left = 0.1234567;
float right= 0.1234566;

const FloatingPoint<float> lhs(left), rhs(right);


if (lhs.AlmostEquals(rhs)) 
{
    std::cout << "EQUAL"; //Shouldnt it have entered here ?
}

任何建议都将不胜感激。

3个回答

9

URL已更改为https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md#floating-point-comparison - nodakai
1
URL 再次更改为 https://github.com/google/googletest/blob/master/docs/advanced.md#floating-point-comparison。 - 10762409

8
你的leftright不是“几乎相等”,因为它们之间的距离太远了,比AlmostEquals的默认公差要大。你链接到的问题中的一个答案显示了4 ULP的公差,但你的数字相差14 ULP(使用IEEE 754 32位二进制和正确舍入软件)。 (ULP是浮点值的最小增量。对于小幅度浮点数来说很小,对于大数来说很大,因此它与数字的大小大约成正比。)
在执行任何浮点比较之前,你应该清楚地了解你正在比较的值中可能存在的错误以及你正在执行的比较。
人们经常错误地说你不能测试浮点值是否相等。这是错误的;执行a == b是一个完美的操作。如果且仅当a等于b时(即,ab是具有完全相同值的数字),它将返回true。实际问题是,他们试图计算给定不正确输入的正确函数。==是一个函数:它接受两个输入并返回一个值。显然,如果你给出任何函数不正确的输入,它可能会返回不正确的结果。因此,这里的问题不是浮点比较;而是不正确的输入。你通常无法在给定不正确的输入的情况下正确计算总和,乘积,平方根,对数或任何其他函数。因此,在使用浮点时,必须设计一种算法来处理近似值(或在特殊情况下,非常小心地确保不引入错误)。
通常,人们尝试通过接受略有不同的数字作为相等的数字来解决其浮点值中的错误。这会减少误报负数(由于先前的计算错误而导致的不等指示),但代价是增加误报正数(由于松散接受而导致的相等指示)。是否可以接受这种一种错误与另一种错误的交换取决于应用程序。没有通用的解决方案,这就是为什么像AlmostEquals这样的函数通常很糟糕的原因。
浮点值中的错误是前面操作和值的结果。这些错误可能从零到无穷大,具体取决于情况。因此,你永远不应该简单地接受诸如AlmostEquals之类的函数的默认公差。相反,应该计算公差,这是特定于他们的应用程序、需求和计算的,并使用该计算公差(或根本不使用比较)。
另一个问题是,诸如AlmostEquals的函数通常使用相对于被比较的值指定的公差编写。然而,值中的错误可能已经受到远不同数量级的中间值的影响,因此最终误差可能是与未出现在被比较的值中的数据有关的函数。

在测试其他代码时,“近似”的浮点比较可能是可以接受的,因为大多数错误很可能会导致大误差,因此对等式的宽松接受将允许良好的代码继续运行,但会报告大多数错误的代码。然而,即使在这种情况下,您也必须适当地设置预期结果和允许的误差容限。 AlmostEquals 代码似乎将误差容限硬编码。


同意,除了像AlmostEquals这样的函数不好这一点。它们只有在你为通用的FloatingPoint定义它们时才是不好的;相反,它们应该为特定于程序的数字类型定义。如果你有像Dose、Coord、Amplitude等类,每个类本质上都是float的包装器,每个类都有自己适用的动态范围和公差,那么就为每个类定义适当类型特定的公差的近似比较。这将防止在代码的不同部分中比较不一致的Dose变量等。 - Michael
2
@Michael: 容差并不由类型决定。例如,参见 Kahan 的 How Futile are Mindless Assessments of Roundoff in Floating-Point Computation? 中的第5节 J-M. Muller's Recurrence 部分。基本上,无论类型如何,这个递归的计算都会收敛到100。数学上正确的极限是5。误差是95 与精度无关。要计算容差,你必须有完整的上下文,包括先前计算和数据的知识。(William Kahan 是我们拥有 IEEE 754 的原因。这篇论文很值得一读。) - Eric Postpischil
我猜这取决于你想做什么。对我来说,每种数字类型的预期容差非常清楚,但这也许是因为每种类型所做的计算类型相对一致。 :) 对于Coord来说,这将是计算几何算法;对于Dose来说,这将是与具有截断支持的内核进行卷积等。我们知道每种类型所做的工作的大致精度,因此可以定义每种类型的容差。另一种选择 - 由于不一致的比较而导致的不明确的假设 - 更糟糕:它们可能会导致可怕的错误。 - Michael
@tmyklebu:不,剂量和坐标(以及其他一些数字类型)存在于不同的宇宙中,即使两者都是浮点数,将它们分开设计是很好的。你通过光圈(Coord)和掩膜(Coord)照射光量(Amplitude),得到光刻胶的照明度(Amplitude),推导出化学上显著的曝光量(Dose),并应用化学反应等来计算出在胶片的每个点(Coord)处有多少胶会被抑制(Dose)。你永远不会将Coord加到Dose中。请注意,如果需要,可以轻松更改Coord从float到int或Amplitude从float到complex。 - Michael
@Michael:当你通过你的东西投射光线时,你最终会对浮点数执行操作。你最终的计算可能出现的误差比开始时更大;每种类型的epsilon并没有太多意义。 - tmyklebu
显示剩余2条评论

2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接