浮点数是我们在编程中常用的一个数据类型, 不知道大家想过没有, 它为什么叫做 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 的形式, 那用这种方式怎么才能表示 0 呢?
这就留作一个小问题让大家去探索吧, 你会发现非规格化, 无穷大, NaN 等有趣的东西.
(完)
来源: https://juejin.im/entry/5ba1a4606fb9a05d0260ea2a