浮点数

Posted by 武龙飞 on August 10, 2019

浮点数简介

32位浮点数都按照 IEEE 规则定义:

  • 首位是符号位,零为正,一为负
  • 8位指数位,真实指数加127为存储位
  • 23位小数位

浮点数表示为$m \times b^e$,$m$ 称作为有效位,或尾数。$b$ 是基数,$e$ 是指数。

浮点数范围

32位浮点数指数位为8位,由于 011111111 指数位有特殊作用,所以只有 [1,254] 范围可以用。因此 $m \times b^e$中,$b$ 为 $2$,$e\in[-126,127]$。$m\in[1, 2-{2^{-23}}]$。所以最大值为:

$max = ({2-{2^{-23}}})\times{2^{127}}$
$min = -({2-{2^{-23}}})\times{2^{127}}$

浮点数精度

浮点数的小数位是归一化的,归一化的意思是小数位只存储自小数点后面的数。32位浮点数的最小精度为 $2^{-23} = 1.192\times{10^{-7}}$。七位有效数字,但随着整数位的增大,小数位的精度下降。比如整数位变为 $2^{23}=8388068$ 则小数位的精度变为 $1$,这时如果在做浮点数和小数的加法,则小数直接被忽略。

浮点数近似值

Intel 8086 处理器被设计用来处理整数运算。这对于使用浮点运算的图形和运算密集型软件是个问题。通过软件模拟浮点运算是可行的,但性能损失很严重。类似于软件AutoCad需要更加强有力的方式来进行浮点数学运算。Intel推出了单独浮点协处理芯片8087,并且随着每代处理器一起升级。直到Intel 486的发布,浮点硬件集成到了主CPU被称作FPU。

浮点数在寄存器里使用IEEE 10-byte 扩展实数格式。当FPU将运算结果放入到内存操作数时,它将结果转换为其中一种格式:整数,长整数,单精度浮点数,双精度浮点数,或者BCD。

浮点目标操作数有效位是有限的,当浮点计算有效位结果长度大于目标操作数有效位长度时,FPU 有以下四种逼近方式:

  • 向最近的偶数舍入:舍入结果最接近无限精确结果。如果两个值非常接近,最终结果以偶数为准(最后一位为零)
  • 向下舍入方向 -∞:舍入结果小于或等于无限精确结果
  • 向上舍入方向 +∞:舍入结果大于或等于无限精确结果
  • 向零舍入(将要舍去的位截断):舍入结果的绝对值小于或等于无限精确结果

FPU控制字包含两位称作RC 字段来标识那种舍入方法。字段值说明如下:

  • 00 二进制:向最近的偶数舍入(默认)
  • 01 二进制:向-∞方向舍入
  • 10 二进制:向+∞方向舍入
  • 11 二进制:向零舍入(截断)

向最近的偶数舍入是默认的舍入方法,此方法被认为是最精确以及被大多数程序实现的方式。

游戏中的浮点问题

游戏中要实现录像,或者网络同步功能,需要在不同的平台运行结果完全一致。如果所有平台使用相同的语言,以及相同的编译器,那浮点运算结果应给一致。但实际上每个平台都有自己的编译器,在每个平台上的CPU的浮点单元运算结果向CPU内存值转换时,存在取舍。IEEE 浮点规则只保证了浮点数的定义,但并有说明转换时的取舍问题。这导致不同平台之间浮点运算结果的差异。总结下来造成相同的代码,执行结果不一致的原因如下:

  1. 编译结果,不同平台编译结果如果不一致,不能保证运算结果一致,比如:a + b + ca + (b + c) 的结果不一定一致
  2. 硬件平台,不同厂商的FPU运算寄存器长度不一致,这导致运算精度不可控
  3. 取舍问题,IEEE没有定义浮点取舍的标准,所以每家语言,平台都有自己的实现接口,这里不能保证统一

游戏开发中为了完成所有平台运算结果一致的问题,使用定点数代替浮点数来保证运算结果一致。

浮点数转换为字符串

游戏中经常需要显示浮点值,显示的内容为字符串。使用有效位1234561.组合出一个值 1.123456,然后再加上 $10^n$,最后结果为$1.123456\times 10^5$。如果指数位为零,直接使用结果$1.123456$。

参考

Demystifying Floating Point Precision
how-to-round-binary-fractions