看完这篇文章,你肯定理解什么是浮点数了!

815 阅读5分钟

浮点数是我们在编程中常用的一个数据类型,不知道大家想过没有,它为什么叫做float呢?

还有,计算机对浮点数的内部表示方法IEEE 874到底是怎么回事?

要彻底理解浮点数,需要从计算机的底层存储开始。

假设有一个32 bit的计算机,需要你来设计一个支持存储“小数”的方案,你会怎么办呢?

定点数

最简单的办法就是把这32位存储分成若干部分, 例如三个部分

(1) 用1位来表达正负位, 0为正, 1为负。

(2) 再划出8位来表示整数部分

(3) 剩下的23位表示小数部分。

就像这样:

上图表示的数值就是182.375,由于小数点固定在了第23位和第24位之间,这种方式可以称为“定点数”。

很明显,由于整数部分的长度比较短,所能表示的数据的范围就比较小。 小数部分比较长, 所能表示的精度就比较高。

我们暂时把这种数据类型叫做fixed number A 。

如果想要表达更大范围的数怎么办? 我们还可以定义一个新的数据类型: fixed number B。 让整数部分扩大一些。

用23位表示整数,这范围比8位大多了, 但是精度又会受到损失了,可见用这种定点数的表示法,范围和精度是一对儿矛盾。

如果再定义fixed number C, fixed number D, 程序员简直就不知道用哪个了, 并且实现他们之间的计算也很麻烦。

所以定点数并不是完美的解决方案。

浮点数

怎么解决定点数的“僵化”问题呢?

我们都知道科学记数法,例如368.79 用科学计数法表示就是 3.6879 * 10 ^2 。

其中3.6879就是尾数,10 是基数, 2 是指数。

浮点数就是利用指数达到了小数点“浮动”的效果。从而可以灵活地表达更大范围内的数, 比如 :

3.6879 * 10 ^ 2 = 368.79

1.2345 * 10 ^ 3 = 1234.5

7.89 * 10 ^ 2 = 789

小数点的位置是不固定的。

不过对于同一个浮点数,也有很多表达方式, 368.79 可以表达为:

3.6879 * 10 ^ 2

0.36879 * 10 ^ 3

36.879 * 10 ^ 1

由于其多样性, 很多计算机厂商都设计了自己的表示浮点数的规则,以及对浮点数运算的细节。 多样的规则对于程序的可靠性和移植性都是不利的。

1976年, 一家叫Intel的公司要设计一个叫做8087的芯片, 这个芯片在8086处理器(这可是大名鼎鼎的芯片啊,计算机系的同学估计很熟悉吧)上增加了支持浮点数的功能。 他们请加州大学伯克利分校的William Kahan教授作为顾问,帮助设计8087芯片。

Intel 还支持Kahan教授加入IEEE资助的制定工业标准的委员会, 最终这个委员会采纳的标准非常接近于Kahan为Intel设计的标准,这就是大名鼎鼎的 IEEE 754 标准。

(码农翻身注:这段描述来自于《深入理解计算机系统》)

该标准在1985年发布,成为了各个计算机厂商都支持的规范,大大提高了程序的可移植性。

最新的标准是2008年发布的 IEEE 754-2008 。

William Kahan教授

IEEE 754

这个标准中有单精度(32位)和双精度(64位), 我们以32位为例来介绍一下,理解了32位,64位就不在话下了。

用科学记数法表示,应该是这样的:

(+ or - ) 1.(mantissa) * 2 ^ exponent

注意:小数点前面是有个1的。

我们接下来用一个例子来计算一下, 把5.8 这个10进制小数转换为IEEE 754表示的浮点数。

首先,5.8 = 1.45 * 2 ^ 2,可能有人问,这是怎么算出来的? 很简单:

5.8/2 => 2.9 

2.9/2 => 1.45 

接下来就可以把他表示成IEEE 754的形式:

(1) 可以看出 符号位 s = 0

(2) 指数(exponent) 是2 吗?

No!  指数也有正负之分,我们既要能用8位二进制数字表示正数,又要能表示负数。

所以2的8次方这256个数字要区分开来使用: 从0到127 表示负数, 从128到255表示正数。

127 被称为 Bias  value (偏置值)

所以exponent = 127 + 2 = 129 , 把129用二进制表示就是10000001。

(注:如果原始的指数是 - 2 , 那exponent 就是 127- 2 = 125)

(3) 尾数mantissa 是0.45 , 需要转化成二进制,怎么做呢? 也很简单,不断地乘2,取结果的整数部分就行,详细过程如下:

可以看出,出现无限循环了,我们取够IEEE 754要求的23位就行了(0.01 1100 1100 1100 1100 1100 1......),可见浮点数是不精确的!

最终,我们把5.8变成了符合IEEE 754 规范的浮点数表示: 

很简单的,对吧?

敏锐的同学可能已经看出问题了,这个尾数总是1.mantissa的形式, 那用这种方式怎么才能表示零呢?  

这就留作一个小问题让大家去探索吧, 你会发现非规格化,无穷大,NaN等有趣的东西。

(完)

码农翻身,用故事讲解技术本质, 更多精彩文章,请移步《码农翻身三年文章精华