以确定性方式处理浮点数
浮点是确定性的。好吧,应该如此。这很复杂。
关于浮点数的文献很多:
以及它们的问题所在:
对于抽象。至少在单个线程上,以相同顺序发生的具有相同数据的相同操作应该是确定性的。因此,我们可以从担心输入和重新排序开始。
导致问题的此类输入之一是时间。
首先,您应该始终计算相同的时间步长。我并不是说不测量时间,而是要您将时间浪费在物理模拟中,因为时间的变化是模拟中的噪声源。
如果不将时间传递给物理模拟,为什么还要测量时间呢?您想测量经过的时间,以知道何时应调用模拟步骤,以及-假设您正在使用睡眠-睡眠了多少时间。
从而:
现在,指令重新排序。
编译器可以确定f * a + b
与相同b + f * a
,但是结果可能不同。它也可以编译为fmadd,或者可以决定采取多行一起发生的情况,并使用SIMD或其他我现在想不到的优化来编写它们。记住,我们希望相同的操作以相同的顺序发生,这是因为我们想要控制发生的操作。
不,使用double不会保存您。
您需要担心编译器及其配置,尤其是要在网络上同步浮点数。您需要使构建版本同意做相同的事情。
可以说,编写汇编将是理想的。这样,您可以决定要执行的操作。但是,这可能是支持多个平台的问题。
从而:
定点数字的情况
由于浮点数在内存中的表示方式,较大的值将失去精度。原因是保持较小的值(钳位)可以缓解问题。因此,没有巨大的速度,也没有很大的房间。这也意味着您可以使用离散物理,因为隧道风险较小。
另一方面,小错误会累积。所以,截断。我的意思是,缩放并转换为整数类型。这样一来,您什么都不会建立。您可以使用整数类型进行一些操作。当需要返回浮点时,可以投射并撤消缩放。
注意我说的是规模。这个想法是1个单位实际上将表示为2的幂(例如16384)。不管它是什么,请使其恒定并使用。基本上,您将其用作定点数。实际上,如果您可以使用一些可靠库中的正确定点数会更好。
我是说截断。关于舍入问题,这意味着您不能信任转换后获得的任何值的最后一点。因此,在投射比例超出所需数量之前,然后将其截断。
从而:
- 保持较小的价值:是
- 仔细取整:是
- 定点数(如果可能):是
等一下,为什么需要浮点数?您不能仅使用整数类型吗?啊对。三角和根。您可以计算三角和辐射的表格,并将其烘焙到源中。或者,您可以使用浮点数来实现用于计算它们的算法,但改用定点数除外。是的,您需要平衡内存,性能和精度。但是,您可以远离浮点数,并保持确定性。
您知道他们为原始PlayStation做过类似的事情吗?请见我的狗,补丁。
顺便说一句,我不是说不要对图形使用浮点数。仅用于物理。我的意思是,当然,这些职位将取决于物理学。但是,如您所知,对撞机不必匹配模型。我们不想看到模型被截断的结果。
因此:使用固定点号。
要明确的是,如果您可以使用编译器来指定浮点的工作方式,而这足以满足您的要求,那么您可以这样做。这并不总是一种选择。此外,我们这样做是为了确定性。定点数字并不意味着没有错误,毕竟它们的精度有限。
我不认为“定点数很难”是不使用它们的充分理由。而且,如果您有充分的理由使用它们,那就是确定性,尤其是跨平台的确定性。
也可以看看:
附录:我建议保持世界的规模很小。话虽如此,两个OP ans和Jibb Smart都提出了偏离原点浮动的精度较低的观点。这将对物理学产生影响,物理学的影响要早于世界边缘。定点数具有固定的精度,它们在任何地方都将同样好(或坏,如果您愿意)。如果我们要确定性,那很好。我还想提到,我们通常做物理学的方式具有放大微小变化的特性。请参阅不可思议的机器和捆绑器中的蝴蝶效应-确定性物理学。
做物理学的另一种方法
我一直在思考,浮点数精度的小误差放大的原因是因为我们正在对这些数字进行迭代。在每个模拟步骤中,我们都取最后一个模拟步骤的结果,并对它们进行处理。在错误之上累积错误。那就是你的蝴蝶效应。
我认为我们不会在同一台机器上看到使用单个线程的单个构建通过相同的输入产生不同的输出。但是,可以在另一台计算机上,也可以在其他版本上。
有一个论点在那里进行测试。如果我们确切地决定事情的工作方式,并且可以在目标硬件上进行测试,则不应发布行为不同的构建。
但是,还有一个论点就是不进行大量修改会累积大量错误。也许这是一个以不同方式进行物理学的机会。
如您所知,存在连续的和离散的物理学,两者都取决于每个对象在时间步长上前进多少。但是,连续物理学具有解决碰撞瞬间的方法,而不是探测不同的可能瞬间以查看是否发生了碰撞。
因此,我提出以下建议:使用连续物理学的技术找出每个对象的下一次碰撞何时发生,而且步长很大,比单个模拟步骤要大得多。然后,您获取最近的碰撞瞬间,并找出该瞬间的所有情况。
是的,单个模拟步骤需要做很多工作。这意味着模拟不会立即开始...
...但是,您可以模拟接下来的几个模拟步骤,而无需每次都检查碰撞,因为您已经知道下一次碰撞的发生时间(或者在较长的时间步中没有碰撞发生)。此外,该模拟中累积的误差是无关紧要的,因为一旦模拟达到较大的时间步长,我们就只需放置预先计算出的位置即可。
现在,我们可以在每个模拟步骤中使用检查碰撞的时间预算来计算发现的下一个碰撞。也就是说,我们可以使用较大的时间步长进行提前模拟。假设世界范围有限(这不适用于大型游戏),那么应该有一排将来的状态用于模拟,然后将每个帧从最后一个状态插值到下一个状态。
我会建议插值。但是,考虑到存在加速度,我们不能简单地以相同的方式进行插值。相反,我们需要考虑每个对象的加速度进行插值。为此,我们可以像在较大时间步长上那样以相同的方式更新位置(这也意味着它不太容易出错,因为我们不会对同一移动使用两种不同的实现方式)。
注意:如果我们正在执行浮点数计算,则此方法无法解决对象离原点距离越远的行为问题。但是,虽然精度确实会随着您离原点的距离的增加而丢失,但这仍然是确定性的。实际上,这就是为什么原本没有提出来的原因。
附录
从OP中进行评论:
这个想法是,玩家将能够以某种格式(例如xml或json)保存他们的机器,以便记录每个棋子的位置和旋转。然后,该xml或json文件将用于在另一台播放器的计算机上重现该机器。
所以,没有二进制格式,对吗?这意味着我们还需要担心恢复的浮点数与原始数是否匹配。请参阅:浮点精度再访:九位数浮点可移植性