【问题标题】:Floating point precision and physics calculations浮点精度和物理计算
【发布时间】:2011-06-21 08:19:41
【问题描述】:

我的物理世界中的重力 Vector2 是 (0; 0.1)。

已知数字 0.1 是有问题的,因为“它不能精确表示,但大约是 1.10011001100110011001101 × 2-4”。

拥有这个重力值会给我带来碰撞问题并产生非常讨厌的错误。将值更改为 0.11 可以解决这些问题。

有没有更优雅的解决方案,根本不需要改变值?


错误视频

http://www.youtube.com/watch?v=bRynch1EtnE


源代码

http://pastebin.com/jNkqa3sg

第一种方法(AABBIsOverlapping)检查两个 AABB 实体之间的交集。 第二个方法(Update)每帧都会为每个body调用。

我将尝试解释 Update 方法的工作原理:

  1. 将重力加速度矢量与速度矢量相加
  2. 创建一个临时向量(下一个)并将其设置为速度
  3. 在当前主体周围的单元格中获取空间散列中的主体
  4. 如果存在水平重叠,解决它,将 next.X 和 velocity.X 设置为 0 并移动播放器
  5. 如果存在垂直重叠,解决它,将 next.Y 和 velocity.Y 设置为 0(或设置为 0.1 以防止从天花板上不断跳跃)并移动播放器
  6. 循环结束后,如果没有重叠,移动播放器

【问题讨论】:

  • 0.11 也无法准确表示。
  • 奇怪...它以某种方式解决了问题。我的猜测是,使用 0.11 会产生足够小的误差,以至于物理引擎不会受到它的影响。
  • 你没有写任何关于你的物理引擎是如何工作的。这很可能是问题所在。
  • 我正在用一些源代码更新帖子。
  • 已添加。您可能还想查看我对 Krom 回复的评论以了解更多说明

标签: c# floating-point physics


【解决方案1】:

简而言之,没有。通常的解决方案是从不检查相等性,始终检查范围 +- epsilon(非常小的值)。

【讨论】:

  • .NET 包含适当的基值 Double.Epsilon 但是您应该缩放它以匹配您的数字的比例:请参阅参考页面上的备注。
  • 从不检查相等性有点强。有时您想这样做,但如果您正在测试的值是浮点计算的结果,则不会。这仍然是一个很好的建议,但远不清楚它是否是 OP 问题的根本原因。
  • @Richard 实际上,您在测试时使用的容差值更多地取决于用于计算被测数字的算法,而不是该数字的比例。
【解决方案2】:

在物理学中,无法表示数字根本不重要。物理学中唯一重要的是舍入误差的累积。

我认为您的问题与不正确的 epsilon 比较有关,甚至与没有 epsilon 的比较有关。但如果没有更多信息,我无法帮助你。如果你的代码不能处理小的舍入错误,那就是有缺陷,需要修复。

您可以使用Decimal 作为您的数学代码,它可以准确地表示0.1m。但我认为这不是您真正需要的,因为您的问题很可能与 0.1 无法在 float 中准确表示这一事实无关。


我在您的代码中看到的一个潜在问题是,在解决碰撞时,您将主体 准确地 移出到碰撞边界。也许您需要将其移出一个 epsilon。

【讨论】:

  • 如果不是因为最后一段非常误导,我会投赞成票。
  • 它在什么方面具有误导性? Decimal 可以代表0.1 但这很可能不是他的问题的解决方案。
  • 它具有误导性,因为可表示性显然与手头的问题无关,但由于 OP 有这种误解,你提供了一种延长它的方法。
【解决方案3】:

如果不可表示性导致您的代码出现错误,那么您的算法在某些方面存在缺陷。很难说它们有什么缺陷,因为您没有分享任何细节,但使用可表示的数字并不能解决问题。

实际上 0.11 也无法表示,因此如果它解决了您的问题,它会出于其他原因这样做。

【讨论】:

    【解决方案4】:

    简而言之 - 您不能像在现实生活中那样在 PC 上使用浮点值。由于用于在非常宽的范围内存储值的内存量有限,总会存在精度损失和舍入误差。

    始终使用一些 epsilon 检查是否相等,这可能是工作值之间的一半,例如0.1:

    IsEqual(0.1, 0.2, 0.05) = false
    IsEqual(0.1, 0.1001, 0.05) = true
    IsEqual(0.1, 0.1499, 0.05) = true
    

    或在给定比例和给定浮点格式下的最佳精度(例如,64 位的 epsilon 显然小于 32 位)(您可能需要检查您的语言以获得该值的方法):

    IsEqual(0.1, 0.2, BestPrecisionAt(0.1)) = false
    IsEqual(0.1, 0.1001, BestPrecisionAt(0.1)) = false
    IsEqual(0.1, 0.1499, BestPrecisionAt(0.1)) = false
    IsEqual(0.1, 0.1000001, BestPrecisionAt(0.1)) = true
    //Where for example BestPrecisionAt(0.1) could be 0.00001
    

    编辑: 您对您遇到的错误只字未提。那么0.1到底有什么问题呢? 我只能假设您的时间步长不够精确,您的对象速度允许它们在碰撞检查之间相互通过。那是对的吗?如果是 - 您应该增加时间步长分辨率和/或更早地检查冲突。

    【讨论】:

    • "那么 0.1 到底有什么问题?" -> 除非身体正好在 Y 128,否则它工作得很好。如果它在 Y 128 并且它下面有障碍物,它就会开始摇晃。它在 Y 32 也有问题:所有东西都被水平推出。破坏游戏的问题。可能我做错了什么——你可以看看源代码,我在原帖中添加了它
    • 看起来在某些级别上,错误会累积,并且每次步骤都会错误地舍入/比较/反转值。检查您的 epsilon 和时间步长。
    • 我没有使用任何 epsilon 或检查时间步长...我应该如何以及在哪里这样做?
    • 我在原帖中添加了一个视频...希望对展示问题有用
    • 添加一个立方体并在调试模式下跟踪您的代码。看看为什么它不会静止不动。可能是一些错误..
    猜你喜欢
    • 2014-03-12
    • 1970-01-01
    • 2017-09-23
    • 1970-01-01
    • 2020-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-18
    相关资源
    最近更新 更多