Avoid floating point precision issues
我即将创建一个移动游戏的地形,允许一个巨大的地形,主要受可用的硬盘空间限制。
这需要将浮点数限制为2个数字的"后点精度"。
在点之后保持2个数字的精度有什么好方法?
请记住:我正在移动设备上运行,因此该方法应该快速且易于使用,并且应该适用于游戏所需的任何算术。
更多信息
我不是在谈论空间(我知道浮动需要多少空间,真的),我在谈论的问题是,当我的浮点数小数点后需要很多数字时,我会松开精度。
使用int会导致我每帧都将int转换为浮点数。我不知道转换的速度有多快,但在为很多对象执行此操作时,这似乎会花费很多性能。 (记得我在移动设备上)。
当然我也不是在谈论地形,我在谈论地形中的物体!地形是一个专门的系统,它实际上可以保持地形大小,从而大大扩展了浮动的极限(当你有足够的磁盘空间时甚至可以在这个系统中保存北美,但实际上限制设置为-99km到+ 99km)。
编辑2
像往常一样,在游戏中,运动是基于时间的,意味着我需要将对象的速度乘以一个给定的修饰符,这会破坏我的数字,这些数字仅限于小数点后的2个数字。
-
int是不够的,只是让它代表你想要的最小精度?如,2位小数。然后浮点问题就会消失。 1.02然后在int土地变为102。
-
从技术上讲,浮子具有更大的精度,那么,你想限制精度吗?请重新编写您的问题以更精确,添加您尝试过的内容以及失败的位置。
-
作为上述评论的附加组件,"2个数字的后点精度"似乎暗示您需要精度到某个单位的1/100。如果将所有单位值乘以100,则表示您具有整数精度值,并且不必使用浮点数。
-
@AdamHouldsworth这是真的,但是我必须在任何地方将int转换成浮点数才能在Unity3D中使用它。
-
@Adam:你最终没有一堆特殊的乘法和除法处理吗?即1.00 * 1.00 = 1.00; 100 * 100 = 10000
-
首先,浮点数是四个字节,无论它们包含什么数字。其次,如果你可以用整数进行存储,那就去做吧。第三,如果你想在一个小空间中代表一个大的地形,那么你将不得不利用某种压缩技术。地形是不可变的吗?如果是这样,那么你可以从不可变的四叉树中获得良好的压缩;这是一个非常标准的技术,适用于具有大量地形资源的游戏
-
我不知道数学,我只是通过建议你试图将它全部整合成整数而不是浮点数来支持这个问题。如果需要比定义更深的准确度,那么我想你会感到头疼。
-
@BinaryWorrier:你经常将山的高度乘以另一座山的高度吗?
-
@Shingetsu我只是想避免我得到像这样的数字1.2302。因为当数字大于25382.27512时它不再精确,并且在25382.27之后我不需要任何数字
-
@EricLippert有趣的方法。我在这里看到你的观点,你应该把它写成答案。
-
@FelixK。如果你想限制精度,你可以在循环时递增0.01,或者通过舍入函数传递它:(也就是说检查点后的3d数是否为5+或更少,并手动指定第二个点后数)
-
@Eric:我不经常玩山地高地没有:),但我可以想象需要将距离除以一个长度。
-
@EricLippert我知道,那不是重点。我要更新我的答案,否则这里的评论会爆炸。
-
@felixk。你如何创建地形?文件中的Heighdata然后循环通过它?预定义的向量?
-
你不必每帧都转换它们。算法的一个基础是记忆。您可以直接存储结果,也可以通过自定义对象存储在字典中(操作数1,2和操作)
-
@Shingetsu我需要每帧转换对象,因为它们在移动。 (不是谈论地形,谈论在地形上移动的物体)。
-
当对象移动时,将最小移动值设置为0.01,并将其增加0.01,以获得更大的输出。
-
在乘法期间,将其向下舍入。像:float result = ...; float modulus = result%0.01; if(modulus>0.05) { result-=modulus; result+= 0.01;} else result-=modulus;
-
如果这个答案足够好,请说出来,所以我把它作为答案发布。
-
@Shingetsu这是一个很好的方法,如果有更快的可能性会很有趣。看一下反汇编,我会做很多操作。
-
@FelixK。从技术上讲,它的O(n)。你有你的结果,然后得到模数(除法,O(1)),然后是一个条件语句与当前静态值比较(所以也是O(1)),然后是2个小动作,如果它是真的和一个小动作如果它是假的。你只会在每次物体移动时都这样做,所以随着移动物体的移动会变得越来越重,所以问题是,你是否计划每米掉落500粒子的雪崩?如果是的话,让我们试着找另一种方法,如果没有,这应该可以正常工作。
-
@BinaryWorrier:如果你按距离划分距离,那么在尺度变化下这是不变的。 1.2 / 0.3与12000.0 / 3000.0相同。
-
好的,让我们回到这里。为什么要限制精度?为什么你的逻辑不那么准确,不太精确,而且当它更准确,更精确,更快时,你的逻辑会变慢?
-
@Shingetsu不是粒子,而是很多角色和鹿。这可以轻松达到500个数字。 (我禁用远处的物体,但如果你有一群200头鹿,一些角色足够了。
-
@EricLippert我认为OP试图通过舍入来避免浮点不精确:answers.unity3d.com/questions/50187/
-
@FelixK。用我的方法200总共没什么。由于该技术的低CPU成本,所以需要几千个可见的移动物体甚至稍微减慢该过程。尝试实现它,看看它是否有效。
-
@FelixK。你可以在我的答案中进一步讨论我的答案! (我现在发布,这应该限制问题中的评论数量)
-
@EricLippert根据msdn,浮点精度限制为7位数。实际上,如上所述,我将我的地形范围限制在-99km到+ 99km。意思是:(+/-)99,999.99米,正好是7位数。我不需要1厘米以下的尺寸,所以我可以忽略它们。但是当我的数字精度大于小数点后的2位数时,我的精度会松散。
-
@FelixK:你需要在200公里的范围内表示一厘米内的位置。让它达到毫米精度只是为了它的高度。只有两亿个不同的点。为它们分配一个整数。 32位整数可以保持+/- 20亿之间的数字。现在,您的所有计算都将舍入到最接近的毫米。
-
@EricLippert我有一种感觉OP希望将运动(力)乘以浮点(时间),因此在这种情况下使用浮点数是有意义的,因为这样的操作的结果是浮点数。他的主要问题是他不想要浮点不精确,并希望将浮点数舍入到0.01精度点。我认为我的解决方案在这种情况下已经足够好了。代表所有职位也是一个有趣的答案,但它不会有助于获得运动。
-
@Shingetsu:好的,然后将位置转换为双打,在双打中完成所有物理,然后在物理完成后将结果转换回int。这样,物理学以尽可能大的精度完成,但位置总是离散的。
-
@EricLippert转换涉及比我的方法更多的时间。除非你的意思是铸造,但铸造直接涉及更原始计划的不精确性。因此,我认为运行基于模数的计算最终会更有效。
-
@EricLippert我在移动设备上。 3D游戏中的双精度计算可能会在移动设备上大幅减慢应用程序的速度。这就是我提出这个问题并寻找最佳解决方案的原因。但无论如何,谢谢你的帮助。
-
@FelixK:双打通常比单打更快,即使在移动设备上也是如此。我相信Windows Phone 7硬件需要在硬件中进行双重算术。你使用什么设备双打比单打慢?
-
@EricLippert我使用iOS-Devices(iPhone4 / iPad和更高版本)并且浮动的性能似乎要快得多。 (stackoverflow.com/questions/1622729/)
-
@FelixK:嗯,我每天都学到新东西。这是我今天的一件事,所以我想回家的时间。 :-)
一种有趣的方法是将其实现到运动功能中:
1 2 3 4 5 6 7
| 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次数学运算。可能的改进是在循环中声明结果和模数OUTSIDE以防止在每次迭代中解除分配 - 重新分配内存(例如: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语言或汇编语言编写,并使用代码混淆(我们知道它可以加快速度)
嗯,无论你选择什么样的精度,他们仍然占用相同的空间。 单身或双人会有所不同,但你应该问的真正问题是你是否需要浮点数。 如果你可以在int中拟合数字,那就这样做。
-
OP希望在Unity3D中使用它。我没看到的是为什么他有漂浮变量的问题。
-
+1表示没有空间通过限制精度来保存。
-
我对节省空间不感兴趣。我喜欢将"后点精度"保持在特定范围内,以避免浮点数的精度问题。
-
您可以在循环中处理它(0.01增量(O(1)),自动向下舍入它们(O(n))或忽略0.01之后的数字(O(1))
-
如果考虑避免浮动,避免浮动的"精确"问题是徒劳的。当你解决所有精确错误的可能性时,你会发明一种设计糟糕且低效且难以维护的定点类型......