浮点数的基本认知


什么是浮点数

很多编程教材和百度出来的文章都会这样说:“计算机中遵循IEEE754规范,小数点飘忽不定的数就是浮点数。”这确实是句废话,你小数点“飘忽不定”,请问“飘忽不定”是怎么体现的,然后背后其原理又是如何的呢?这些被笼统描述甚至不阐述就导致了读者一知半解。
接下来我们利用科学计数法对浮点数开展学习:

8.355 = 8.355 *10 ^0
8.355 = 83.55 *10 ^-1
8.355 = 835.5 *10 ^-2
8.355 = 8355 *10 ^-3
83550000000 = 8.355 *10 ^10
计算机中的表达:
8.355 === 8.355e0
8.355 === 83.55e-1
8.355 === 835.5e-2
8.355 === 8355e-3
83550000000 === 8.355e10

顺理成章的我们将以上的科学计数法总结出一个通用表达式:V = (-1)^S * M * R^E
S:符号位,取值 0 或 1,决定一个数字的符号,0 表示正,1 表示负。
M:尾数,用小数表示,例如前面所看到的 8.355 * 10^0,8.355 就是尾数
R:基数,表示十进制数 R 就是 10,表示二进制数 R 就是 2
E:指数,用整数表示,例如前面看到的 10^-1,-1 即是指数

浮点数用法

从例子出发

接下来我们自定义一些规则,看看如何在32bit中表示一个浮点数。

如图所示,我们定义如下规则:

  • 符号位 S 占1bit
  • 指数 E 占10bit
  • 尾数 M 占21bit
  • (D)代表十进制,(B)代表二进制

现在我们将12.125转换为32bit浮点数。
整数:12(D) = 110(B)
小数:0.125(B)) = 0.001(B)
所以:110.001(B) = 12.125(D) = 1.10001 *2 ^2(B)
按自定义规则排列得到:0 1100000000 110001000000000000000
注意: 以上得到的结果不是标准的浮点数排列,而是为了举例子自定义的。

总结

当我们指定的指数位数越大时,浮点数的范围就越大,反之越小。而尾数尾数越大时浮点数精度就越高,反之精度就越低。
早期人们提出浮点数定义时,就是这样的情况,当时有很多计算机厂商,例如IBM、微软等,每个计算机厂商会定义自己的浮点数规则,不同厂商对同一个数表示出的浮点数是不一样的。
这就会导致,一个程序在不同厂商下的计算机中做浮点数运算时,需要先转换成这个厂商规定的浮点数格式,才能再计算,这也必然加重了计算的成本。
那怎么解决这个问题呢?业界迫切需要一个统一的浮点数标准。

浮点数统一标准

直到1985年,IEEE组织推出了浮点数标准,就是我们经常听到的IEEE754浮点数标准,这个标准统一了浮点数的表示形式,并提供了 2 种浮点格式:

  • 单精度浮点数 float:32 位,符号位 S 占 1 bit,指数 E 占 8 bit,尾数 M 占 23 bit
  • 双精度浮点数 float:64 位,符号位 S 占 1 bit,指数 E 占 11 bit,尾数 M 占 52 bit

为了使其表示的数字范围、精度最大化,浮点数标准还对指数和尾数进行了规定:

  • 尾数 M 的第一位总是 1(因为二进制的数只有 1 和 0),因此这个 1 可以省略不写,它是个隐藏位,这样单精度 23 位尾数可以表示了 24 位有效数字,双精度 52 位尾数可以表示 53 位有效数字
  • 指数 E 是个无符号整数,表示 float 时,一共占 8 bit,所以它的取值范围为 0 ~ 255。但因为指数可以是负的,所以规定在存入 E 时在它原本的值加上一个中间数 127,这样 E 的取值范围为 -127 ~ 128。表示 double 时,一共占 11 bit,存入 E 时加上中间数 1023,这样取值范围为 -1023 ~ 1024。

针对特定情况,还有以下规定:

  • 指数 E 全 0,尾数非 0:非规格化数,尾数隐藏位不再是 1,而是 0(M = 0.xxxxx),这样可以表示 0 和很小的数
  • 指数 E 全 1,尾数全 0:正无穷大/负无穷大(正负取决于 S 符号位)
  • 指数 E 全 1,尾数非 0:NaN(Not a Number)

回到开篇的例子,我们用统一标准将12.125转换为32bit浮点数看看。
整数:12(D) = 110(B)
小数:0.125(B)) = 0.001(B)
所以:110.001(B) = 12.125(D) = 1.10001 *2 ^2(B)
指数:2 + 127 = 129(D) = 10000001(B)
按统一标准排列得到:0 10000001 10001000000000000000000

精度和范围

精度

浮点数在计算机中存在精度损失。
用“乘二取整,顺序排列”的方法计算 0.2(D) 的二进制数。

0.2 * 2 = 0.4 -> 0
0.4 * 2 = 0.8 -> 0
0.8 * 2 = 1.6 -> 1
0.6 * 2 = 1.2 -> 1
0.2 * 2 = 0.4 -> 0 无限循环
...
结果:0.2(D) = 0.00110011...(B)

由于尾数的尾数限制,0.00110011...(B)只会取到限定的位数。这样就导致了尾数精度的损失。
单精度浮点数:0.00000000000000000000001(B) => (1/2)^23(D)
同理,双精度浮点数:(1/2)^52(D)

范围

单精度浮点数:最大范围1.111...111 *2 ^127(B) ~= 2 ^128(D) ~= 3.4 *10 ^38, 最小范围则是在最大范围前面加上负号即可-3.4 *10 ^38
同理,双精度浮点数:-2 ^1024 ~= -1.79 *10 ^3081.79 *10 ^308