浅谈计算机浮点数

计算机是二进制的,只有 0 和 1。负数可以用一个符号位表示,然后用补数计算。但是小数的表示就有点特别。这让我感觉有点搞。所以特地记一下。

定点数

既然说到浮点数就顺便提一下定点数。

定点数就是小数点的个数是固定的,使用定点数必须要知道小数点的个数。比如如果知道数据的范围是 -9,999,999.99 ~ 99,999,999.99 那么可以得到小数点后需要精确两位,根据数据范围可以直到需要用 5 个字节存储。因此 -4,325,120.25 可以表示为:

1
00010100    00110010    01010001    00100000    00100101

转化成 16 进制就是:

1
14h 32h 51h 20h 25h

注意最左边的半个字节构成的 1 表示符号位。这就是定点数,很直观,因为知道数据就是有两位小数,所以直接默认最后一个字节保存小数。定点数就是小数点固定的数。

浮点数

浮点数就是小数点不固定的数,正好跟定点数相对。

转化成二进制之后可以发现,用科学计数法表示,小数点左边一般都为1。所以存储的时候通常是忽略这个 1 的。因为默认存在,所以不去做存储。

拿单精度举例。
float

最前面一位为符号位,然后 8 位指数位,然后 23 位为有效数。可以用 s (符号位) ,e (指数) 以及 f (有效数) 来描述它:

$$(-1)^s \times 1.f \times 2^{e-127}$$

贴几个例子就会比较清楚,看懂 wiki 上这几个例子就明白浮点数在计算机是怎么存储的了:

1
2
3f80 0000 = 0 01111111 00000000000000000000000 = 1
c000 0000 = 1 10000000 00000000000000000000000 = −2

1
2
7f7f ffff = 0 11111110 11111111111111111111111 = (1 − 2−24) × 2128 ≈ 3.402823466 × 1038  (max finite positive value in single precision)
0080 0000 = 0 00000001 00000000000000000000000 = 2−126 ≈ 1.175494351 × 10−38 (min normalized positive value in single precision)
1
2
0000 0000 = 0 00000000 00000000000000000000000 = 0
8000 0000 = 1 00000000 00000000000000000000000 = −0
1
2
7f80 0000 = 0 11111111 00000000000000000000000 = infinity
ff80 0000 = 1 11111111 00000000000000000000000 = −infinity
1
2
4049 0fdb = 0 10000000 10010010000111111011011 = 3.1415927410 ≈ π ( pi )
3eaa aaab = 0 01111101 01010101010101010101011 ≈ 1/3

另外有一个地方需要注意。如果当 e 为255,但是 f 不为 0 的时候,通常别解释为“不是一个数”,被缩写为 NaN,表示未知的数或非法操作的结果。运算中如 NaN * 0 = NaN。这个地方比较需要注意。要知道我们计算机中并不是任何东西乘 0 都为 0 的。

双精度也是同理的。

比较定点数和浮点数

定点数的精度是明确的,也就是说,定点数是均匀的。而浮点数不一样,但是整数部分很大的时候,精度是很差的。任何语言中浮点数的运算都是出现有误差的。通常浮点数 (0,1) 区间内很多,但是整数部分越大,也就越稀疏。比如 262144.00 和 262144.01 在单精度浮点数中存储的是同一个数。所以在商业软件中是不使用浮点数的。使用浮点数还是要谨慎。

有一个地方比较特别。就是浮点数存在 NaN 这个东西,在很多地方是需要注意的。比如编译器的优化,x = y * 0 是不能优化成 x = 0,虽然对于整型来说,这个优化是正确的,可以减少一条汇编语句,但是对于浮点数的话是错误的,如NaN * 0 = NaN