计算机是如何存储小数的?在C语言编程中,应该注意什么吗?


计算机是如何存储小数的?在C语言编程中,应该注意什么吗?

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

文章插图
该浮点数的值等于尾数乘以 2^x 。读者应该注意 。上图是二进制分数 。因此 0.1表示 1/2 。为了方便理解 。我们可以将其与十进制的小数对应起来:十进制的 0.1 等于 1*10^-1 。所以二进制的 0.1 等于1*2^-1 。也即 1/2 。
“尾数+指数”模式存储浮点数可能有一点问题 。例如:2x10^-1=0.2x10^0=0.02x10^1 。依此类推 。同样一个数字可能有多种“尾数+指数”的表示方法 。而同时兼顾多种表示方法势必会造成巨大的浪费(也可能使在硬件中实现数学操作变得困难和缓慢) 。
所以 。“尾数+指数”的存储模式需要一个统一的标准 。事实上 。IEEE-754 确实已经有标准了:假设给定一个二进制的浮点数 。那么除非这个数是 0 。否则总有某个位是 1 。将小数点移到第一个 1 之后 。调整指数位 。这样一来 。“尾数+指数”的唯一存储方式就固定下来了 。也即“1.m x 2^n”形式 。
既然小数点前总是 1 。那么上述标准下的“尾数+指数”的存储模式甚至都不需要再花费空间存储小数点前的 1.
计算机是如何存储小数的?在C语言编程中,应该注意什么吗?

文章插图
现在读者可能又有疑问了 。因为 1.0 =1.0×2^0 。上述存储模式不存储小数点前的 1 。也即尾数和指数部分都为 0 。而“如果数字的每一位都为零 。那么数字就被认为是零” 。这样看来 。1.0 似乎是没有办法存储的 。
当然可以存储 1.0 。单精度浮点数的指数部分是“shift-127”编码的 。也即实际的指数等于 eeeeee 减去 127 。所以 1.0 的表示方法实际上是 1.0×2^127 。同样的道理 。最小值本应该是 2^-127 。按照“shift-127”编码指数部分 。也即 2^0 。可是这样又变成“指数部分和尾数部分都为零”了 。因此在该标准下的最小值 。实际上的写法是 2^1 。也即 2^-126 。
在我看来 。为了表示 0 和 1 。舍弃最小值(2^-127)是非常可取的做法 。
零不是唯一的“特殊情况” 。对于正无穷大和负无穷大 。非数字(NaN) 。以及没有数学意义的结果(例如 。非实数 。或无穷大乘以零之类的计算结果)也有表示:如果指数的每一位都等于1 。那么这个数字是无穷大 。如果指数的每一位都等于1 。并且尾数位也都等于1 。那么这个数字就是NaN 。符号位仍然区分+/-inf和+/-nan 。
现在 。读者应该明白IEEE-754浮点数的表示方法了 。下面是几个数字的表示方法:
计算机是如何存储小数的?在C语言编程中,应该注意什么吗?

文章插图
作为程序员 。了解浮点表示的某些特性是很重要的 。下标列出了单精度和双精度IEEE浮点数的示例值:
计算机是如何存储小数的?在C语言编程中,应该注意什么吗?

文章插图
注意 。本文中的所有数字都假定为单精度浮点数;上面包含双精度浮点数用于参考和比较 。
在C语言程序开发中 。数值的处理是一门值得深究的科学 。本文不可能将复杂的数值算法以及相关的C语言程序开发经验一一列出 。事实上 。讨论如何以理想的数值精度进行计算 。就和讨论如何编写最快的C语言程序 。如何设计一款优秀的软件一样 。主要取决于程序员本身的综合素质 。
鉴于此 。这里将尝试介绍一些基础的 。我认为每个C语言程序员都应该知道的内容 。
相等
首先 。我们应该明白C语言程序开发中的两个浮点数何时相等 。可能读者并不觉得难 。因为似乎C语言中的 == 运算符就能判断两个浮点数是否完全相等 。
计算机是如何存储小数的?在C语言编程中,应该注意什么吗?

文章插图
读者应该已经明白 。计算机存储浮点数时 。很有可能是需要舍弃一些位的(如果该浮点数过长) 。如果 CPU 或者相应的程序没有按照预期四舍五入 。那么使用 == 运算符判断两个浮点数是否相等可能会失败 。
例如 。标准C语言函数库三角函数 cos() 的实现其实只是一种多项式近似 。也就是说 。我们并不能指望 cos(π/2) 结果的每一个位都为零 。在C语言程序开发中 。我们甚至不能准确的表示 π 。
看到这里 。读者应该思考“相等到底是什么意思呢?” 。对于大多数情况来说 。两个数“相等”意味着这两个数“足够接近” 。本着这种精神 。在实际的C语言程序开发中 。程序员通常定义一个很小的值模拟“足够接近” 。并以此判断两个浮点数是否“足够接近到相等” 。例如:

推荐阅读