我在一个通用标题中使用了 typedef,并使用 float 作为我的默认值。
typedef real_t float;
我不建议为此使用模板,因为当您尝试使用多态/虚拟函数时,它会导致巨大的设计问题。
为什么浮动有效
花车对我来说效果很好,原因有 3 个:
首先,几乎每个物理模拟都需要在力和扭矩上添加一些噪音以实现真实感。这种随机噪声的幅度通常远大于浮点数的精度。
其次,在许多情况下,有限的精度实际上是有益的。考虑到几乎所有关于刚体的经典力学都不适用于现实世界,因为没有完美的刚体。因此,当您对不完美的刚体施加力时,您不会获得完美的加速度到第 7 位。
第三,许多模拟的持续时间很短,因此累积的误差仍然足够小。使用双精度不会自动改变这一点。创建与现实世界相匹配的长时间运行的模拟非常困难,并且将是非常专业的项目。
当浮动不起作用时
这是我不得不考虑使用双精度的情况。
- 纬度和经度应该是两倍。对于这些数量,对于大多数用途来说,浮点数根本就没有足够好的分辨率。
- 随着时间的推移计算非常少量的积分。例如,高斯马尔可夫过程是表示传感器偏差中随机游走的好方法。然而,这些值通常会非常小并且会累积。浮点数的计算错误可能比双精度数大得多。
- 超越通常的刚体线性和旋转运动经典力学的专业模拟。例如,如果您使用蛋白质分子、晶体生长、微重力物理等进行操作,那么您可能想要使用 double。
当双打不起作用时
实际上,双倍伤害的更高精确度有时会受到伤害,尽管这种情况很少见。 What every computer scientists should know... 的一个例子:如果你有一些随着时间的推移收敛到 1 的数量。如果结果为 0,则获取它的日志并执行某些操作。使用 double 时,您可能永远不会达到 1,因为可能不会发生舍入,但使用浮点数可能会发生。
另一个例子:您需要使用special code 来比较实际值。这些代码通常具有默认舍入到 epsilon,对于浮点数来说是相当合理的 1E-6,但对于其 1E-15 来说是两倍。如果您不小心,这可能会给您带来很多惊喜。
性能
还有一个惊喜:在现代 x86 硬件上,float 与 double 的原始性能几乎没有区别。内存对齐、缓存等几乎比浮点类型占主导地位。在我的机器上,对 100M 随机数和浮点数的简单求和测试耗时 22 秒,双倍数需要 25 秒。所以浮点数确实快了 12%,但我仍然认为它太低而不能仅仅为了性能而放弃双精度。但是,如果您使用 SSE 指令或 GPU 或 Arduino 等嵌入式/移动硬件,那么浮点数会更快,这肯定是驱动因素。
除了刚体的线性和旋转运动之外什么都不做的物理引擎可以在当今的桌面级硬件上以 2000Hz 的频率在单线程上运行。您可以轻松地将其并行化到许多内核。许多简单的低端模拟只需要 50Hz。在 100Hz 时,事情开始变得非常顺利。如果你有 PID 控制器之类的东西,你可能必须提高到 500Hz。但即使在这种最差情况下,您仍然可以使用足够好的桌面模拟大量对象。
总之,除非您实际衡量它,否则不要让性能成为您的驱动因素。
做什么
经验法则是使用尽可能多的精度来使代码正常工作。对于刚体的简单物理引擎,浮动通常就足够了。但是,您希望能够在不修改代码的情况下改变主意。因此,最好的方法是使用开头提到的 typedef,并确保您的代码既适用于浮点数,也适用于双精度数。然后经常测量并随着项目的发展选择类型。
在您的案例中,另一件重要的事情是:保持物理引擎与渲染系统完全分离。物理引擎的输出可以是双精度或浮点型,并且应该根据渲染系统的需要进行类型转换。