相等首先,我们应该明白C语言程序开发中的两个浮点数何时相等 。可能读者并不觉得难,因为似乎C语言中的 == 运算符就能判断两个浮点数是否完全相等 。
然而实际上,C语言中的 == 运算符是逐位比较两个操作数的,而两个浮点数的精度总是有限的,在这种场景下,== 运算符的实际使用意义就没有那么大了 。
== 运算符的实际使用意义没有那么大
读者应该已经明白,计算机存储浮点数时,很有可能是需要舍弃一些位的(如果该浮点数过长),如果 CPU 或者相应的程序没有按照预期四舍五入,那么使用 == 运算符判断两个浮点数是否相等可能会失败 。
例如,标准C语言函数库三角函数 cos() 的实现其实只是一种多项式近似,也就是说,我们并不能指望 cos(π/2) 结果的每一个位都为零 。在C语言程序开发中,我们甚至不能准确的表示 π 。
看到这里,读者应该思考“相等到底是什么意思呢?”,对于大多数情况来说,两个数“相等”意味着这两个数“足够接近” 。本着这种精神,在实际的C语言程序开发中,程序员通常定义一个很小的值模拟“足够接近”,并以此判断两个浮点数是否“足够接近到相等”,例如:
#define EPSILON 1.0e-7#define flt_equals(a, b) (fabs((a)-(b)) < EPSILON)宏 flt_equals(a, b) 正是通过判断 a 和 b 的距离是否小于 EPSILON(10的-7次方),来断定 a 和 b 是否可以被认为“相等”的 。这样的近似模拟技术有时候是有用的,有时候却可能导致错误结果,读者应该自行判断它是否符合自己的程序 。
读者应该自行判断它是否符合自己的程序
在本例中,EPSILON 可以看作是一种用于说明程序精度的标尺 。应该明白,衡量精度的应该是有效数字,纠结 EPSILON 的具体大小并无意义,下面是一个例子 。
假设在某段C语言程序中有两个数字 1.25e-20 和 2.25e-20,它俩的差值是 1e-20,远小于 EPSILON,但是显然它俩并不相等 。但是如果这两个数字是 1.2500000e-20和1.2500001e-20,那么就可以认为它们是相等的 。也就是说,两个数字距离足够接近时,我们还需要关注需要匹配多少有效数字 。
溢出计算机存储空间总是有限的,因此数值溢出是C语言程序员最关心的问题之一 。读者应该已经知道,如果向C语言中的最大无符号整数加一,该整数将归零,令人崩溃的是,我们并不能只通过看这个数字的方式获知是否有溢出发生,归零的整数看起来和标准零一模一样 。
当溢出发生时,实际上大多数 CPU 是会设置一个标志位的,如果读者懂得汇编,可以通过检查该标志位获知是否有溢出发生 。float 浮点数溢出时,我们可以方便的使用 +/- inf(无穷) 。+inf(正无穷)大于任何数字,-inf(负无穷)小于任何数字,inf+1 等于 inf ,依此类推 。因此在C语言程序开发中,一个小技巧是,将整数转换为浮点数,这样就方便判断后续处理是否会造成溢出了 。处理完毕后,再将该数转换回整数即可 。
将整数转换为浮点数,就方便判断后续处理是否会造成溢出了
不过,将整数转换为浮点数判断是否溢出也是要付出代价的,因为浮点数可能没有足够的精度来保存整个整数 。32 位的整数可以表示任何 9 位十进制数,但是 32 位的浮点数最多只能表示 7 位的十进制数 。所以,如果将一个很大的整数转换为浮点数,可能不会得到期望的结果 。
此外,在C语言程序开发中,int 与 float 之间的数值类型转换,包括 float 与 double 之间的数值类型转换,实际上是会带来一定的性能开销的 。
读者应该明白,在C语言程序开发中,不管是否使用整数,都应该小心避免数值溢出的发生,不仅仅是最开始和最终结果数值可能溢出,在一些计算的中间过程,可能会产生一些更大的值 。一个经典的例子是“C语言数字配方”计算复数的幅度问题,极可能造成数值溢出的C语言实现是下面这样的:
double magnitude(double re, double im){ return sqrt(re*re + im*im);}假设该复数的实部 re 和虚部 im 都等于 1e200,那么它们的幅度约为 1.414e200,这的确在双精度的允许范围内 。但是,上述C语言代码的中间过程将产生 1e200 的平方值,也即 1e400,这超出了 inf 的范围,此时上面的实现函数计算的平方根将仍然是无穷大 。
谨防计算中间值溢出
因此,magnitude() 函数的更佳C语言实现如下:
double magnitude(double re, double im){ double r; re = fabs(re); im = fabs(im); if (re > im) { r = im/re; return re*sqrt(1.0+r*r); } if (im == 0.0) return 0.0; r = re/im; return im*sqrt(1.0+r*r);}
推荐阅读
- C语言5种必须要搞懂的常量
- IP地址冲突的解决方法
- 男士瘦臀运动有哪些?
- 社保卡的初始密码是什么?每个银行是一样的吗?
- 哪些瑜伽动作能减去腿部脂肪
- 冲泡红茶最合适的水温 95℃
- 肯德基有没有早餐 肯德基每个店都有早餐吗
- 一般蚊子可以飞多高 蚊子能飞多高
- 在Go语言中如何串联 HTTP 处理程序
- 每天什么运动最减肥呢?