每个C语言程序员都应该明白,计算机究竟是如何存储小数的?

浮点型在内存中的存储分布方式因机器平台而异,完全理解所有机器平台中的浮点型存储无疑是一件相当麻烦的事 。幸运的是,大多机器平台都遵守 IEEE-754 标准,很可能读者和我使用的平台正是使用的 IEEE-754 标准 。

每个C语言程序员都应该明白,计算机究竟是如何存储小数的?

文章插图
计算机是如何存储浮点数的呢?
IEEE-754是如何存储浮点数的?IEEE-754浮点(32位)或双精度(64位)有三个部分(在IEEE-854下也有类似的96位扩展精度格式):符号位,表示数字是正的还是负的;指数位;以及指定实际数字的尾数位 。以C语言中的单精度浮点数为例,下面是某位浮点数的位布局:
每个C语言程序员都应该明白,计算机究竟是如何存储小数的?

文章插图
某位浮点数的位布局
该浮点数的值等于尾数乘以%202^x 。读者应该注意,上图是二进制分数,因此%200.1表示%201/2 。为了方便理解,我们可以将其与十进制的小数对应起来:十进制的%200.1%20等于%201*10^-1,所以二进制的%200.1%20等于1*2^-1,也即%201/2 。
“尾数+指数”模式存储浮点数可能有一点问题,例如:2x10^-1=0.2x10^0=0.02x10^1,依此类推 。同样一个数字可能有多种“尾数+指数”的表示方法,而同时兼顾多种表示方法势必会造成巨大的浪费(也可能使在硬件中实现数学操作变得困难和缓慢) 。
所以,“尾数+指数”的存储模式需要一个统一的标准 。事实上,IEEE-754%20确实已经有标准了:假设给定一个二进制的浮点数,那么除非这个数是%200,否则总有某个位是%201 。将小数点移到第一个%201%20之后,调整指数位,这样一来,“尾数+指数”的唯一存储方式就固定下来了,也即“1.m%20x%202^n”形式 。
既然小数点前总是%201,那么上述标准下的“尾数+指数”的存储模式甚至都不需要再花费空间存储小数点前的%201.
但是如果数字是零呢?IEEE%20Standards%20Committee%20通过将零作为一种特殊情况来解决这一问题:如果数字的每一位都为零,那么数字就被认为是零 。
1.0%20似乎是没有办法存储的
现在读者可能又有疑问了,因为%201.0%20=1.0×2^0,上述存储模式不存储小数点前的%201,也即尾数和指数部分都为%200,而“如果数字的每一位都为零,那么数字就被认为是零”,这样看来,1.0%20似乎是没有办法存储的 。
当然可以存储%201.0 。单精度浮点数的指数部分是“shift-127”编码的,也即实际的指数等于%20eeeeee%20减去%20127,所以%201.0%20的表示方法实际上是%201.0×2^127 。同样的道理,最小值本应该是%202^-127,按照“shift-127”编码指数部分,也即%202^0,可是这样又变成“指数部分和尾数部分都为零”了,因此在该标准下的最小值,实际上的写法是%202^1,也即%202^-126 。
在我看来,为了表示%200%20和%201,舍弃最小值(2^-127)是非常可取的做法 。
零不是唯一的“特殊情况” 。对于正无穷大和负无穷大,非数字(NaN),以及没有数学意义的结果(例如,非实数,或无穷大乘以零之类的计算结果)也有表示:如果指数的每一位都等于1,那么这个数字是无穷大,如果指数的每一位都等于1,并且尾数位也都等于1,那么这个数字就是NaN 。符号位仍然区分+/-inf和+/-nan 。
【每个C语言程序员都应该明白,计算机究竟是如何存储小数的?】现在,读者应该明白IEEE-754浮点数的表示方法了,下面是几个数字的表示方法:
每个C语言程序员都应该明白,计算机究竟是如何存储小数的?

文章插图
几个数字的表示方法
作为程序员,了解浮点表示的某些特性是很重要的,下标列出了单精度和双精度IEEE浮点数的示例值:
每个C语言程序员都应该明白,计算机究竟是如何存储小数的?

文章插图
单精度和双精度IEEE浮点数的示例值
注意,本文中的所有数字都假定为单精度浮点数;上面包含双精度浮点数用于参考和比较 。
在C语言程序开发中,数值的处理是一门值得深究的科学 。本文不可能将复杂的数值算法以及相关的C语言程序开发经验一一列出 。事实上,讨论如何以理想的数值精度进行计算,就和讨论如何编写最快的C语言程序,如何设计一款优秀的软件一样,主要取决于程序员本身的综合素质 。
鉴于此,这里将尝试介绍一些基础的,我认为每个C语言程序员都应该知道的内容 。


推荐阅读