Unity 浮点数的精度问题


前言

说到浮点数精度,大家想到的就是double比float的精度高,想要高精度就用double类型。两者最明显的区别就是所占位数的不同,也就是字节不同,float是32位占8字节,double则是64位占8字节。因此,运算效率也不同。


一、精度问题

为什么会有精度问题呢?计算机只能存0和1,让计算机存储1/3这种无限循环小数在二进制中无法精确表示。因此,计算机在存储1/3这样的数据时会进行近似处理,导致在一定精度范围内的误差。

计算机在记录浮点数时通常采用 IEEE 754 标准。这个标准定义了浮点数的存储格式和算术运算规则。每个浮点数通常由三部分组成:符号位(表示正负)、尾数(表示有效数字)和指数(表示数值的数量级)。根据这个标准,浮点数的存储格式是二进制的,采用科学计数法表示。符号位决定了数值的正负,尾数和指数组合在一起确定了数值的精度和范围。

1、数值不相等

不知道大家遇到过阈值触发逻辑的时候,实用全等号(==)判断浮点数会出现不准确的问题,比如在值等于0.545的时候出发逻辑a,值会不断增加减少,但每次增加的值小于0.01,实际数值根本对不上0.545这个数,要么大要么小。解决办法可能是实用大于、小于符号,但是结果并不准确。如果想稳妥的解决可以使用绝对值控制浮动区间,减少误差,也就是ABS(X-Y)<0.00001。

2、数值计算不确定

在计算机中,浮点数的表示是有限的,因此在执行某些计算时,可能会出现舍入误差。举个例子,假设你有三个变量:( x = 1f ),( y = 2f ),( z=(1f /5555f)×11 110f)。

现在,假设你有一个条件判断,要求当 x/z<0.5f时执行某些操作。从逻辑上来说,你可能会认为 ( \frac{x}{z} ) 也应该满足这个条件,因为你把 ( z ) 视为等同于 ( y ),即等于2。然而,在计算机内部,由于浮点数的位数限制,( z ) 的计算结果可能是接近但不完全等于2的数,例如0.4999999999991。当然,也可能会得到一个大于0.5的结果,而不符合你的预期。这种情况会导致外层条件判断为真,但内层条件判断为假,造成程序的异常行为。在编写代码时,需要特别注意浮点数的精度问题,并谨慎处理条件判断,以避免出现意外的结果。

3、不同设备计算结果不同

因为不同平台的浮点数计算方式不同,结果也会有所区别。

二、解决方法:

(1)使用一台计算机计算,只计算一次,将其作为这次结果。通常在前后端同步时使用,客户端的结果由服务器进行结算,客户端进行展示效果。实际中更复杂些。
(2)计算时使用int或long,在显示时显示浮点数。
(3)实用定点数,因为定点数整数和小数部分数分开的,可以都看作整数。我们在计算时重载运算符,进行计算即可;只是其中涉及到部分数学运算,不是计科类的专业整理学习时间可能较长,不过Git上有不少已经写好的方案,可以拿来学习和修改。
(4)使用string字符串进行计算,精度需求很大的时候才会使用,可以控制精度,但十分消耗性能。


总结

浮点数精度问题是计算机中普遍存在的挑战。由于计算机存储和计算的限制,浮点数在进行运算时往往会产生舍入误差,导致结果不够准确。为了解决这个问题,我们可以采取一些策略,如控制精度、使用定点数等。在程序开发中,需要特别注意处理浮点数的精度,以确保程序的正确性和稳定性。

### Unity 中 `float` 类型的精度问题 在开发过程中,特别是在处理大型场景或高精度需求的应用时,Unity 的 `Vector3.position` 使用的是 `float` 数据类型。由于 `float` 只能提供大约 7 位有效数字,在表示非常大或非常小数值时会出现显著误差[^1]。 #### 浮点数的本质缺陷 浮点数存储方式决定了其固有的局限性。IEEE 754标准定义了单精度(即 `float`)和双精度(即 `double`),其中前者占用32位而后者则为64位。对于大多数游戏而言,默认采用 `float` 已经足够;但在某些特殊情况下——比如涉及天文尺度物体的位置计算——这种选择可能会引发不可忽视的问题[^3]。 ```csharp // 示例展示 float 和 double 表现差异 void ShowPrecisionDifference() { Vector3 posFloat = new Vector3(2.073282f, -1.106536f, 0); Vector3 posDouble = new Vector3((float)2.073282d, (float)-1.106536d, 0); Debug.Log($"Using Float: {posFloat}"); Debug.Log($"Using Double: {posDouble}"); // 转换回 float 输出可能仍存在舍入错误 } ``` #### 解决方案探讨 为了克服上述挑战,开发者们提出了多种策略: - **原点偏移技术**:通过动态调整世界坐标的中心点来减少绝对距离带来的影响。这种方法允许局部区域内保持较高的相对定位准确性。 - **自定义数据结构**:利用更高精度的数据类型如 `double` 或者专门设计用于精确算术运算的大整数类库来进行内部计算。不过需要注意的是,这通常意味着牺牲一定的性能效率[^4]。 - **固定小数点机制**:类似于引用中提到的方法,使用长整形 (`long`) 来模拟带有特定数量的小数位的有效范围内的实数操作。这种方式虽然能够很好地控制并提高精度,但也增加了编码复杂性和潜在的风险。 ```csharp public struct FixedPointNumber { private const int FRACTIONAL_PLACES = 32; private readonly long rawValue; public static implicit operator FixedPointNumber(float value){ return new FixedPointNumber(value * ONE); } public static explicit operator float(FixedPointNumber fpn){ return ((float)fpn.rawValue / ONE); } private const long ONE = 1L << FRACTIONAL_PLACES; internal FixedPointNumber(long rawValue){ this.rawValue = rawValue; } } FixedPointNumber num = 5.5f; // 自动转换 Debug.LogError($"{num} as float is {(float)num}"); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值