避免浮点精度问题

Posted

技术标签:

【中文标题】避免浮点精度问题【英文标题】:Avoid floating point precision issues 【发布时间】:2012-03-20 13:41:59 【问题描述】:

我即将为手机游戏创建一个地形,该地形允许巨大的地形,但主要受可用硬盘空间的限制。

这需要将浮点数限制为 2 个数字的“点后精度”。 有什么好的方法可以让它在点后保持 2 个数字的精度?

请记住:我在移动设备上运行,因此该方法应该快速且易于使用,并且应该适用于游戏所需的任何算术。

更多信息

我不是在谈论 空间(我知道浮点数需要多少空间,真的),我说的是当我的浮点数将有时我失去精度的问题到小数点后的多个数字。

使用int 会导致我必须将 int 转换为每帧的浮点数。我不知道转换的速度有多快,但是在为很多对象执行此操作时,这似乎会花费很多性能。 (记住我在移动设备上)。

当然我也不是在谈论地形,我是在谈论地形中的物体!地形是一个专门的系统,它实际上可以容纳一个延伸的地形大小浮动的限制很多(当你有足够的磁盘空间时,甚至可以在这个系统中保存北美,但实际上限制设置为 -99km 到 +99km )。

编辑 2

与游戏中的往常一样,运动是基于时间的,这意味着我需要将对象的速度乘以统一给我的修饰符,这会破坏我的数字,限制为小数点后 2 个数字。

【问题讨论】:

int 是否还不够,只是让它代表您想要的最小可能精度?如,2 位小数。然后浮点问题就消失了。 1.02 然后在int 土地中变为 102。 首先,浮点数是四个字节,无论它们包含什么数字。其次,如果你能以整数进行存储,那就去做吧。第三,如果你想在一个小空间里表现一个大地形,那么你将不得不利用某种压缩技术。地形是不变的吗?如果是这样,那么您可能能够从不可变四叉树中获得良好的压缩;对于具有大量地形资源的游戏来说,这是一种相当标准的技术。 好的,让我们退后一步。 为什么要限制精度?你为什么要让你的逻辑不太准确不太准确,以及更慢,而实际上它可以更准确,更精确和更快 @FelixK.:您需要在 200 公里范围内表示位置在一厘米以内。让我们把它提高到毫米精度只是为了它。只有两亿个不同的点。为它们中的每一个分配一个整数。一个 32 位整数可以容纳 +/- 20 亿之间的数字。现在所有您的计算将四舍五入到最接近的毫米。 @EricLippert 我有一种感觉 OP 想要将运动(力)乘以浮点数(时间),因此在这种情况下使用浮点数是有意义的,因为这种操作的结果是浮点数。他的主要问题是他不希望浮点不精确,而是希望将浮点数舍入到 0.01 精度点。我认为我的解决方案在这种情况下已经足够好了。代表所有位置也是一个有趣的答案,但它无助于移动。 【参考方案1】:

一种有趣的方法是将其实现到运动功能中:

float result = //multiply force by time
float modulus = result%0.01f;
result -= modulus; //you will do this in any case
if(modulus>=0.005f) /*round up. if you want it to only round down, remove
  the next 2 lines, if you want it to only round up, remove
  the conditional statement*/
  result+=0.01f; 

我无法考虑如何进一步优化它,我删除了 else 语句并让它无条件地带走模数,因为无论如何它都会完成。

【讨论】:

做了一些测试,只要不移动很多物体,性能似乎还可以。 这就是我一直在说的^^。数学运算实际上只占用很少的性能,并且,在基础上,这只是每个对象的 2-3 次数学运算。一个可能的改进是在循环外声明结果和模数,以防止在每次迭代中重新分配内存(例如:float result=0, modulus=0; //begin the loop where you just use them rather then declare them again and again 请注意,舍入逻辑具有远离零的偏差,因为它不采用所谓的“银行家舍入”。 a) 使用二进制数作为最小值:而不是使用 0.01f。如果您不介意,请使用 0.0078125。这比带有填充尾数的除法要快,因为它相当于一个简单的移位操作。 b)使用除法而不是使用逆乘法,因为乘法比除法更快。好的,通常编译器会得到它,但原则仍然存在。 c) 完全是以下操作:乘以 128,下限(下限的快速替换是加 16777216 再减 16777216),然后乘以 1/128。 @ThorstenS。一种。从技术上讲,我必须实施演员表。铸造也需要时间。所以没有什么大的区别(甚至可能更慢,这是 C# 还记得吗?) b.这是 .NET,JIC 编译器负责处理它。 C。这使代码不可读。人们通常会使用更高级别的语言,尽管它会降低效率,以使代码更具可读性和更易于管理。如果 OP 正在寻求压缩性能的每一点,他会用 C 或汇编编写,并使用代码混淆(我们知道它会加快速度)【参考方案2】:

嗯,无论您选择以何种精度进行操作,它们仍然占用相同数量的空间。单或双会有所不同,但您应该问的真正问题是您是否需要浮点数。如果您可以将数字放入 int 中,请执行此操作。

【讨论】:

OP 想在 Unity3D 中使用它。我不明白为什么他对浮动变量有问题。 我对节省空间不感兴趣。我喜欢将“后点精度”保持在特定范围内,以避免浮点数的精度问题。 你可以让它在循环中处理(0.01增量(O(1)),自动向下舍入(O(n))或忽略超过0.01的数字(O(1))跨度> 避免浮动的“精度”问题是徒劳的,如果担心避免浮动的话。当您消除了所有潜在的精度误差时,您将发明一种设计糟糕的低效且无法维护的定点类型......

以上是关于避免浮点精度问题的主要内容,如果未能解决你的问题,请参考以下文章

Swift 中如何避免精度丢失

浮点型数据

JAVA 中出现double 及float计算丢失精度的问题,大家是怎么避免的

单精度和双精度浮点值有啥不同? [复制]

了解浮点精度

js 浮点小数计算精度问题 parseFloat 精度问题