如何处理浮点数舍入错误
How to deal with float rounding errors
我正在尝试在 Java 中为游戏实现基本的 2D 矢量数学函数。它们会被游戏密集使用,所以我希望它们尽可能快。
我开始使用整数作为向量坐标,因为游戏不需要更精确的坐标,但对于所有计算,我仍然必须更改为双精度向量以获得清晰的结果(例如,两条线之间的交点) .
使用双精度,存在舍入误差。我可以简单地忽略它们并使用类似
的东西
d1 - d2 <= 0.0001
来比较这些值,但我假设通过进一步计算,错误可能会累积起来,直到变得很重要。所以我想我可以在每次可能不精确的操作之后对它们进行舍入,但结果会产生更糟糕的结果,假设是因为程序也舍入了不精确的值(例如 0.33333333...
-> 0.3333300...
)。
使用 BigDecimal 会太慢。
解决这个问题的最佳方法是什么?
方法不准确
当你使用需要精确计算的数字时,你需要确保你没有在做类似的事情:(这就是你目前正在做的事情)
这将导致随着过程的继续进行舍入误差的累积;长期为您提供极其不准确的数据。在上面的例子中,你其实是开始四舍五入float
4次,每次都越来越不准确!
准确方法
一种更好更准确的获取数字的方法是这样做的:
这将帮助您避免舍入误差的累积,因为每次计算仅基于 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
我正在尝试在 Java 中为游戏实现基本的 2D 矢量数学函数。它们会被游戏密集使用,所以我希望它们尽可能快。
我开始使用整数作为向量坐标,因为游戏不需要更精确的坐标,但对于所有计算,我仍然必须更改为双精度向量以获得清晰的结果(例如,两条线之间的交点) .
使用双精度,存在舍入误差。我可以简单地忽略它们并使用类似
的东西d1 - d2 <= 0.0001
来比较这些值,但我假设通过进一步计算,错误可能会累积起来,直到变得很重要。所以我想我可以在每次可能不精确的操作之后对它们进行舍入,但结果会产生更糟糕的结果,假设是因为程序也舍入了不精确的值(例如 0.33333333...
-> 0.3333300...
)。
使用 BigDecimal 会太慢。
解决这个问题的最佳方法是什么?
方法不准确
当你使用需要精确计算的数字时,你需要确保你没有在做类似的事情:(这就是你目前正在做的事情)
这将导致随着过程的继续进行舍入误差的累积;长期为您提供极其不准确的数据。在上面的例子中,你其实是开始四舍五入float
4次,每次都越来越不准确!
准确方法
一种更好更准确的获取数字的方法是这样做的:
这将帮助您避免舍入误差的累积,因为每次计算仅基于 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