【问题标题】:How to deal with float rounding errors如何处理浮点舍入错误
【发布时间】:2015-10-16 15:23:21
【问题描述】:

我正在尝试用 Java 为游戏实现基本的 2D 矢量数学函数。它们将被游戏密集使用,所以我希望它们尽可能快。

我从整数作为矢量坐标开始,因为游戏不需要更精确的坐标,但对于所有计算,我仍然必须更改为双矢量以获得清晰的结果(例如,两条线之间的交点)。

使用双精度数时会出现舍入误差。我可以简单地忽略它们并使用类似的东西

d1 - d2 <=  0.0001

比较这些值,但我假设通过进一步的计算,错误可以总结起来,直到它变得显着。所以我想我可以在每次可能不精确的操作之后对它们进行四舍五入,但结果却产生了更糟糕的结果,假设是因为程序也会对不精确的值进行四舍五入(例如。0.33333333... -> 0.3333300...)。

使用 BigDecimal 太慢了。

解决这个问题的最佳方法是什么?

【问题讨论】:

  • 如果你的游戏只有整数坐标,你一定会在某些时候失去一些精度,不是吗?但应该清楚的是,无需舍入并不是提高结果准确性的好方法。人们通常只是忍受浮点的不准确性,如果您知道自己在做什么,它就可以正常工作。如果一个数字有一点偏差,你的游戏中可能发生的最糟糕的情况是什么?
  • 对于线的交叉点,这种舍入误差只对非常小的角度很重要
  • 准确到什么程度才足够准确?你真的需要这种精确度吗?玩家会注意到吗?
  • 我会尽可能简单地实现它,使用双精度。内置舍入是由数值近似和舍入误差专家精心设计的。您不太可能通过添加自己的舍入来改进它。
  • 就像您建议的那样,使用可接受的精度级别的 epsilon 值,例如0.0001.

标签: java floating-point precision floating-accuracy


【解决方案1】:

方法不准确

当您使用需要精确计算的数字时,您需要确保您没有做类似的事情:(这就是您目前正在做的事情)

随着过程的继续,这将导致舍入误差的累积;长期为您提供极其不准确的数据。在上面的例子中,你实际上是把开始的float四舍五入4次,每次它变得越来越不准确!


准确的方法

获取数字的更好、更准确的方法是这样做:

这将帮助您避免舍入误差的累积,因为每次计算仅基于 1 次转换,并且该转换的结果不会复合到下一次计算中。

最好的攻击方法是从必要的最高精度开始,然后根据需要进行转换,但保持原样不变。我建议您按照我发布的第二张图片中的流程进行操作。

我从整数作为矢量坐标开始,因为游戏不需要更精确的坐标,但对于所有计算,我仍然必须更改为双矢量以获得清晰的结果(例如,两条线之间的交点)。

请务必注意,如果对最终结果没有明显影响,则不应尝试对值进行任何类型的舍入;你只会做更多的工作而几乎没有收获,如果做得足够多,甚至可能会降低性能。

【讨论】:

  • 好答案。您通常区分游戏状态视觉表现游戏状态 是根据需要精确的变量(它们是此答案中的浮点数),并根据它们之前的值在每个游戏滴答时更新(但不是基于显示状态)。在此更新之后,其他变量(视觉表示、辅助变量)使用 游戏状态 派生。对于视觉推导,这称为rendering
【解决方案2】:

这是对prior answer 的一个小补充。将浮点数转换为整数时,重要的是舍入而不是强制转换。在以下程序中,d 是严格小于 1.0 的最大双精度数。它很容易作为计算的结果而出现,该计算在无限精确的实数算术中会产生 1.0。

简单转换得到结果 0。先四舍五入得到结果 1。

public class Test {
  public static void main(String[] args) {
    double d = Math.nextDown(1.0);
    System.out.println(d);
    System.out.println((int)d);
    System.out.println((int)Math.round(d));
  }
}

输出:

0.9999999999999999
0
1

【讨论】:

    猜你喜欢
    • 2021-04-16
    • 1970-01-01
    • 2019-12-15
    • 2014-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多