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


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

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

文章插图
在本例中 。EPSILON 可以看作是一种用于说明程序精度的标尺 。应该明白 。衡量精度的应该是有效数字 。纠结 EPSILON 的具体大小并无意义 。下面是一个例子 。
假设在某段C语言程序中有两个数字 1.25e-20 和 2.25e-20 。它俩的差值是 1e-20 。远小于 EPSILON 。但是显然它俩并不相等 。但是如果这两个数字是 1.2500000e-20和1.2500001e-20 。那么就可以认为它们是相等的 。也就是说 。两个数字距离足够接近时 。我们还需要关注需要匹配多少有效数字 。
溢出
计算机存储空间总是有限的 。因此数值溢出是C语言程序员最关心的问题之一 。读者应该已经知道 。如果向C语言中的最大无符号整数加一 。该整数将归零 。令人崩溃的是 。我们并不能只通过看这个数字的方式获知是否有溢出发生 。归零的整数看起来和标准零一模一样 。
当溢出发生时 。实际上大多数 CPU 是会设置一个标志位的 。如果读者懂得汇编 。可以通过检查该标志位获知是否有溢出发生 。
计算机是如何存储小数的?在C语言编程中,应该注意什么吗?

文章插图
不过 。将整数转换为浮点数判断是否溢出也是要付出代价的 。因为浮点数可能没有足够的精度来保存整个整数 。32 位的整数可以表示任何 9 位十进制数 。但是 32 位的浮点数最多只能表示 7 位的十进制数 。所以 。如果将一个很大的整数转换为浮点数 。可能不会得到期望的结果 。
此外 。在C语言程序开发中 。int 与 float 之间的数值类型转换 。包括 float 与 double 之间的数值类型转换 。实际上是会带来一定的性能开销的 。
读者应该明白 。在C语言程序开发中 。不管是否使用整数 。都应该小心避免数值溢出的发生 。不仅仅是最开始和最终结果数值可能溢出 。在一些计算的中间过程 。可能会产生一些更大的值 。一个经典的例子是“C语言数字配方”计算复数的幅度问题 。极可能造成数值溢出的C语言实现是下面这样的:
计算机是如何存储小数的?在C语言编程中,应该注意什么吗?

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

文章插图
因此 。magnitude() 函数的更佳C语言实现如下:
计算机是如何存储小数的?在C语言编程中,应该注意什么吗?

文章插图
应该明白 。上述C语言代码为了避免数值溢出 。给出的实现实际上是一种近似 。例如 im 等于 1e200 。re 等于 1 。那么 im/re 的平方仍然能够达到 1e400 。这会造成数值溢出 。但是平方 re/im 却是可以的 。因为 1e-400 会被四舍五入到零 。足够接近得到正确的答案 。
有效数字丢失
上面讨论的浮点数精度 。以及相等问题只是C语言程序数值运算中的冰山一角 。“有效数字丢失”指的是一类情况 。在这种情况下 。程序员很可能丢失数值的准确性 。
计算机是如何存储小数的?在C语言编程中,应该注意什么吗?

文章插图
幸运的是 。就像上面求复数幅度避免出现数值溢出一样 。避免两个接近的数字相减出现精度损失的方法也是有的 。但是并没有一个通用的方法 。这里给出一个简单的实例就是使用 1/x 的函数代替 x 的函数 。这对于处理二次运算很有效 。我的建议是 。如果读者发现自己的C语言程序给出了令人怀疑的数值 。就应该检查一下相应的减法运算了 。
看到这里 。相信读者应该想到C语言程序中的加法可能也有同样的问题:假设有数字 1.0 。现在将其与 1e-20 相加 。程序很可能认为 1e-20 很小 。小于 EPSILON 。于是忽略它 。得到答案 1.0 。这实际上也是一种精度损失 。
经验法则
计算机是如何存储小数的?在C语言编程中,应该注意什么吗?

文章插图
【计算机是如何存储小数的?在C语言编程中,应该注意什么吗?】正如前文举的例子 cos(π/2) 。C语言它的实现实际上是一种近似 。得到的答案并不是 0 。而是 6.12303E-17 。不过就这个答案本身而言 。它已经足够小 。认为等于 0 也没什么大问题 。但是如果我们下一步计算是除以 1e-17 。那么得到的答案就约是 6 了 。这与预期的零相差甚远 。
不要忘记整数
最后 。在C语言程序开发中 。并不是只有浮点数才重要的 。整数同样重要 。它的精确性是一个有用的工具 。有时程序需要跟踪变化的分数(例如比例因子) 。在这种情况下 。既然浮点数受各种因素影响 。那么我们完全可以将该分数存储为整数分子和分母来避免问题 。在需要使用浮点数时 。随时再做一次除法运算就可以了 。

推荐阅读