使用浮点数表示小数在各种开发中都非常常用,但是在游戏开发中有一些场合需要使用整数来模拟定点数代替浮点数表示小数,本篇会简单讲解这两种表示小数方法的区别和使用场景。
浮点数
现行的浮点数通用运算标准是由 IEEE 委员会在上世纪八十年代制定的,被称为 IEEE 754 标准,它规定了浮点数需要使用 $v$ = $(-1)^s$ * $f$ * $2^e$ 的形式来表示一个小数:
- s 代表符号位(sign),当 s 为 1 时 v 是负数,s 为 0 时 v 是正数。
- f 代表小数(fraction)部分。
- e 代表指数(exponent)部分,它可以为负数。
一般在单精度浮点数(float)中 s 占 1 位,e 占 8 位,f 占 23 位,一共 32 位。在双精度浮点数(double)中 s 占 1 位,e 占 11 位,f 占 52 位,一共 64 位。
虽然有了 IEEE 754 标准,但是各家在实现上还是有一些区别,尤其是舍入规则上。这导致了跨平台,尤其是跨 CPU 架构的情况下,执行同一个浮点数计算得到的结果可能不一样。相关内容可以参考这篇回答,Cross Platform Floating Point Consistency。
由于大部分情况下的开发都不太需要浮点数满足跨平台一致性,所以一般功能的日常开发并无什么影响,但是游戏开发中如果使用了帧同步技术的话,微小的计算差异可能会被慢慢累积,最后变得使得多端的状态不一致。
定点数
定点数是指小数点固定的数,一般是通过为整数约定一个固定的小数点位置来模拟小数。早期因为 CPU 的浮点计算能力比较差,定点数被广泛使用,比如经典游戏 DOOM 中就使用了定点数。
由于现代 CPU 的浮点计算能力大幅提升,绝大多数情况下已经完全不需要使用定点数模拟小数了,跨平台小数计算一致性就是定点数为数不多的使用场景之一。
定点数的实现其实不太难,一般在精度要求不高的情况下,使用 32 位整数模拟一个 16.16 的小数即可满足大部分需求,来看一下 DOOM 的实现。
|
|
m_fixed 提供了乘除法的接口,举一个例子来测试一下定点数和浮点数。
|
|
可以看到定点数的精度是不如浮点数的,并且使用同样大小的空间时,定点数可以表示的范围也远不及浮点数。
定点数的最大的优势是自主实现,规则可控,这就意味着可以非常轻易的做到跨平台的一致性。在部分开发场景下,一致性远比高精度更加重要。