为具有浮点不准确性的代码编写有意义的单元测试(例如,碰撞检测)

时间:2016-04-24 21:39:00

标签: unit-testing floating-point physics-engine

让我们说我正在使用花车编写带碰撞检测的物理引擎。一种方法可能是检查两个物理对象是否相交或接触。

class PhysicsObject {

    Vector3f position;

    [...]

    public void isIntersecting(PhysicsObject otherObject) {
        boolean isTouchingOrIntersecting = [do calculations];
        return isTouchingOrIntersecting;
    }

}

对于模拟本身,浮点精度(即使使用浮点数)也足够好,因为任何不准确都不会显而易见(并在下一个模拟步骤中立即纠正)。

但是我应该如何编写我的单元测试,尤其是边界情况呢?我可以用远处的两个物体编写测试,两个物体明显交叉,但它们如何完全接触?根据浮点值,该方法可以返回true或false,具体取决于任何类型的外部条件(编译器,体系结构等)。

或者我应该说这个方法在这个边界情况下返回的结果并不重要,因此为这种情况编写单元测试是没有意义的吗?

当方法的返回值再次成为浮点数时,我很清楚我应该使用相对误差和epsilons。但是我将这个碰撞检测问题作为整个类似问题的一个例子,当浮点被转换为某些"精确的" (布尔,整数)结果,以及如何测试(在3D图形/物理代码的上下文中)。

1 个答案:

答案 0 :(得分:2)

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,但位置计算因您无法找到交叉点而发散,则可能最终处于无限循环或崩溃。