持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情
前言
关于C语言中的三大零值不知道大家了解多少呢,还有浮点数比较问题,浮点数能够直接用比较运算符进行比较吗?为什么呢?
本文就来分享一波作者的学习心得与见解。
笔者水平有限,难免存在纰漏,欢迎指正交流。
三大零值
0
\0
NULL
它们的值都是0,但是有区别:类型的区别
0是int类型
转到定义可以查看到:NULL 是((void *)0),也就是指针类型
'\0'是char类型
然而类型的区别只是给使用者和编译器来区分的,实际上值都是0,对于计算机没什么区别。
C语言有没有bool类型
c99之前,主要是c90是没有的,目前大部分书,都是认为没有的。因为书,一般都要落后于行业。
但是c99引入了_Bool类型(你没有看错,_Bool就是一个类型,不过在新增头文件stdbool.h中,被重新用宏写成了bool,为了保证C/C++兼容性)。
实际上通常使用C89和C90,也就是bool类型用的比较少,直接用伪bool类型(一般是int)由非零为真零为假来判断,而且一般这样:if(flag),直接用变量值去和零比较。
实际上,如果你使用的是VS的话,会发现还有一个BOOL类型,对应TRUE和FALSE,为什么会这样呢?这是微软设计的只适合微软工具的类型,不具备可移植性,不推荐使用。
指针变量与“零值”进行比较
int *p = NULL;
//(1)
if(p == 0)
//(2)
if(p)
//(3)
if(NULL == p)
其实都可以,但是哪个比较好呢?
(1)写法可能引起误会,有可能把p认为是整型变量或其他类型
(2)写法也同(1),可能会认为是“bool”类型
(3)写法更为稳妥些,毕竟看到NULL立马就能认出p应该是指针类型
浮点数的比较问题
首先我们要明确一个点:浮点数存储时并不是完整存储的,是有可能产生精度损失的,这与浮点数在内存中的存储方式有关,以前的文章已经讲过了,这里不再赘述。文章链接:[深入浅出C语言]浅析浮点数 - 掘金
注意这里的损失并不是一味的减少小,还有可能增大。浮点数本身存储的时候若遇到计算不尽的情况会“四舍五入”。
浮点数和0.0比较
#include <stdio.h>
int main()
{
double x = 1.0;
double y = 0.1;
printf("%.50f\n", x - 0.9);
printf("%.50f\n", y);
if ((x - 0.9) == y)
{
printf("you can see me!\n");
}
else
{
printf("oops\n");
}
return 0;
}
很显然,浮点数的存和取并不那么准确。
可能有人就会说:把%.50f改成%.1f是不是就没问题了,毕竟精度精确到了小数点后一位嘛,后面的全部四舍五入了呗。
那我们试试看:
int main()
{
double x = 1.0;
double y = 0.1;
printf("%.1f\n", x - 0.9);
printf("%.1f\n", y);
if ((x - 0.9) == y)
{
printf("you can see me!\n");
}
else
{
printf("oops\n");
}
return 0;
}
这样一来好像打印没什么问题了,但是还是不能用==比较欸。其实上面修改的精度是printf()函数打印浮点数的精度,实际上浮点数存储时很可能存在精度损失,这是难以避免的,有可能比理想值高一点或低一点,无法满足==运算的精确要求。
结论:因为精度损失问题,两个浮点数,绝对不能使用==进行相等比较。
两个浮点数该如何比较
应该进行范围精度比较,只要计算结果符合给定的精度范围要求,我们在误差允许范围内就认为该比较结果可靠。
对于==比较,需要两数之差在给定的精度范围内,可以有两种写法:
//伪代码
if((x-y) > -精度 && (x-y) < 精度)
{
//TODO
}
//伪代码-简洁版
if(fabs(x-y) < 精度)//fabs是浮点数求绝对值
{
//TODO
}
对于>比较,需要两数之差大于精度值:
//伪代码
if(x-y > 精度)
{
//TODO
}
对于<比较,需要两数之差小于精度值:
//伪代码
if(x-y < 精度)
{
//TODO
}
精度:
如果有需要,可以试试自己设置,通常是宏定义。
一般推荐使用系统精度:DBL_EPSILON和FLT_EPSILON
#include<float.h> //使用下面两个精度,需要包含该头文件
DBL_EPSILON //double 最小精度
FLT_EPSILON //float 最小精度
//两个精度定义
#define DBL_EPSILON 2.2204460492503131e-016
#define FLT_EPSILON 1.192092896e-07F
XXX_EPSILON是最小误差,是:XXX_EPSILON+n不等于n的最小的正数。
EPSILON这个单词翻译过来是'ε'的意思,数学上,就是一个极小的正数 。
代码调整后
#include <stdio.h>
#include <math.h> //必须包含math.h,要不然无法使用fabs
#include <float.h> //必须包含,要不然无法使用系统精度
int main()
{
double x = 1.0;
double y = 0.1;
printf("%.50f\n", x - 0.9);
printf("%.50f\n", y);
if (fabs((x - 0.9) - y) < DBL_EPSILON)//原始数据是双精度浮点数,我们就用DBL_EPSILON
{
printf("you can see me!\n");
}
else
{
printf("oops\n");
}
return 0;
}
和0.0比较
int main()
{
double x = 0.00000000000000000000001;
//有三种写法:
if (fabs(x-0.0) < DBL_EPSILON)//写法1
{
printf("you can see me!\n");
}
if (fabs(x) < DBL_EPSILON)//写法2
{
printf("you can see me!\n");
}
if(x > -DBL_EPSILON && x < DBL_EPSILON)//写法3
{
printf("you can see me!\n");
}
else
{
printf("oops\n");
}
return 0;
}
可不可以出现等号
x > -DBL_EPSILON && x < DBL_EPSILON: 为何不是>= && <= 呢?
个人看法:
XXX_EPSILON是最小误差,是:XXX_EPSILON+n不等于n的最小的正数。
XXX_EPSILON+n不等于n的最小的正数: 有很多数字+n都可以不等于n,但是XXX_EPSILON是最小的,而XXX_EPSILON依旧是引起不等的一员。
换句话说:fabs(x) <= DBL_EPSILON(确认x是否是0.0的逻辑),要是相等了,就说明x本身就相当于最小误差DBL_EPSILON了,已经能够引起其他与它相加减的数据本身的变化了,可是这个不符合0.0不能引起其他数据变化的原则,就矛盾了,所以建议是不要加上等号,只要在精度范围内就可以说明是0.0了(小于了最小误差,就认为是0.0,实际上值不是真的0.0)。
以上就是本文全部内容,感谢观看,你的支持就是对我最大的鼓励~