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


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

文章插图
magnitude() 函数的更佳C语言实现
应该明白,上述C语言代码为了避免数值溢出,给出的实现实际上是一种近似 。例如 im 等于 1e200,re 等于 1,那么 im/re 的平方仍然能够达到 1e400,这会造成数值溢出 。但是平方 re/im 却是可以的,因为 1e-400 会被四舍五入到零,足够接近得到正确的答案 。
有效数字丢失上面讨论的浮点数精度,以及相等问题只是C语言程序数值运算中的冰山一角 。“有效数字丢失”指的是一类情况,在这种情况下,程序员很可能丢失数值的准确性 。
前面我们提到,1.m 的尾数形式确保小数点前总是 1(非零时),既然如此,我们没有必要再花费一个位的空间用于存储 1 。此时,即使尾数只有最后一位为 1,其他位都为 0,那么它的有效数字也是全部的,因为最前方有个“隐藏的 1” 。但是,如果两个比较接近的浮点数相减,这个“隐藏的1”就会被抵消,最终得到的答案可能只剩下一位有效数字的精度 。
如果两个比较接近的浮点数相减,这个“隐藏的1”就会被抵消
幸运的是,就像上面求复数幅度避免出现数值溢出一样,避免两个接近的数字相减出现精度损失的方法也是有的,但是并没有一个通用的方法 。这里给出一个简单的实例就是使用 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语言程序开发中,并不是只有浮点数才重要的,整数同样重要,它的精确性是一个有用的工具 。有时程序需要跟踪变化的分数(例如比例因子) 。在这种情况下,既然浮点数受各种因素影响,那么我们完全可以将该分数存储为整数分子和分母来避免问题 。在需要使用浮点数时,随时再做一次除法运算就可以了 。
 




推荐阅读