假设我正在使用浮点数编写一个具有碰撞检测功能的物理引擎。一种方法可能是检查两个物理对象是否相交或接触。
class PhysicsObject {
Vector3f position;
[...]
public void isIntersecting(PhysicsObject otherObject) {
boolean isTouchingOrIntersecting = [do calculations];
return isTouchingOrIntersecting;
}
}
对于仿真本身,浮点精度(即使使用浮点数)就足够好了,因为任何不准确性都不会可见/明显(并在下一个仿真步骤中立即纠正)。
但是我应该如何编写我的单元测试,尤其是对于边界情况?我可以用两个远距离的物体和两个明显相交的物体来编写测试,但是它们究竟接触怎么样?根据浮点值,该方法可以根据任何类型的外部条件(编译器、体系结构等)返回 true 或 false。
或者我应该说方法在此边界情况下返回哪个结果并不重要,因此为这种情况编写单元测试毫无意义?
当该方法的返回值再次为浮点数时,很明显我应该使用相对误差和 epsilons。但是我使用这个碰撞检测问题作为整个类似问题的示例,当浮点转换为一些"精确"(布尔值,整数)结果时,以及如何测试这些问题(在3D图形/物理代码的上下文中)。
IEEE算术不是随机的。它是可逐位重现的。因此,如果它可以在一台机器和编译器上运行,它将在其他机器和编译器上运行,但需要注意。由于中间计算精度的差异,FLT_EVAL_METHOD={0,1,2} 的值将产生不同的结果。您可以使用 #ifdefs
#if FLT_EVAL_METHOD == 0
EXPECT_EQ(answer0, result);
#elif FLT_EVAL_METHOD == 1
EXPECT_EQ(answer1, result);
#else /* FLT_EVAL_METHOD == 2 */
EXPECT_EQ(answer2, result);
#endif
以在这些不同的结果之间进行排序。不幸的是,当您使用浮点计算来做出决定时,这是唯一的方法。好消息是 3 路开关就足够了。我有一些单元测试可以做到这一点,它们在许多机器和编译器上完美运行。
你可以对浮点比较做同样的事情,但如果你只关心近似相等,你可以使用像googletest的EXPECT_DOUBLE_EQ(a,b)这样的东西,它检查4个ULP(最后一个位置的单位)内的相等性。其工作方法是首先将浮点值解释(使用联合)为有符号量级整数,将其转换为多余的 XX 整数(合并 ±0),然后检查差值是否为 <= 4。这将自动生成一个相对度量值,该度量值降级为接近零的绝对度量值。
确保使用类似类型的计算来确定是否存在交集以及相交位置。实际上,编写一个单元测试来确保这一点,并在边界处对其进行检查,将位置调整为 ±1、±2、...ULP 以确保它们一致。如果交叉点查询指示 true,但位置计算因找不到交叉点而发散,则最终可能会陷入无限循环或崩溃。