如何处理浮点数舍入错误

How to deal with float rounding errors

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

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

使用双精度,存在舍入误差。我可以简单地忽略它们并使用类似

的东西
d1 - d2 <=  0.0001

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

使用 BigDecimal 会太慢。

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

方法不准确

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

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


准确方法

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

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

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

I started with integers as the vector coordinates because the game needs nothing more precise for the coordinates, but for all calculations I still would have to change to double vectors to get a clear result (eg. intersection between two lines).

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

这是对 的一个小补充。将浮点数转换为整数时,重要的是舍入而不是强制转换。在下面的程序中,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