C语言中double与float的比较
目录
目录
2.原因分析
3.问题"解决"
4.何为精度损失
4.浮点数的正确比较方法
1.问题再现
2.最终解决方案
1.问题发现
在编程中我们时常要判断两个数是否相等,我们先来看一段简单的代码,猜猜运行结果是什么:
#include<stdio.h>
int main()
{
float t=3.197;
if (t == 3.197)
{
printf("hehe");
}
else
printf("haha");
return 0;
}
你是否也和我一样认为t恒等于3.197,先别急,让编译器来告诉我们答案:
2.原因分析
为什么会打印“haha呢”,难道编译器出错了?实则不然。
其实,当我们写下小数时,编译器会自动把它当做double类型:
而由于我们的t是float类型,占4个字节,而double类型占8个字节,由于浮点数在内存中以IEEE754标准进行存放,就会出现精度损失(下面讲解)的问题。当float与double类型进行比较时,不同类型存放数据的精度不同,编译器就会认为不相同,打印输出“haha”。
3.问题"解决"
那么,该如何解决这个问题使最后程序运行的结果为我们想要的答案?你可能会想到以下几种方法:
1.将变量t定义为double类型
double t=3.197;
2.在小数后加f使其变为float类型
float t=3.197f;
3.使用()运算符将3.197强制类型转化为double类型
float t =(float)3.197;
通过以上改进,程序成功输出“hehe”:
4.何为精度损失
无论单精度浮点数还是双精度浮点数,在计算机中都是以IEEE754的方式来进行存放。
我们知道float占32个比特位,其在计算机内存中存放的格式如下:
而double占64个比特位,其在计算机内存中存放的格式如下:
其中符号位1表示负,0表示正,阶码部分用指数部分的移码表示,而尾数部分正是决定精度的重要部分,对于不同的类型,其能表示的精度不同,从上图可看出double类型表示比用float类型表示精度更高。
当小数转化为二进制时,往往会出现无限位数的情况,而数据显然不能无限存储,这时计算机就要进行截取,而截取的位数就取决于尾数的位数。
正因如此,计算机常常出现精度丢失的问题。这就是为什么上述问题会引起歧义的原因。
此外,当double隐式转化为float类型时,由于位数不同,计算机也会实行截断,造成精度丢失。值得注意的是,一个浮点数的的整数部分存储在尾数位置的高位,因此发生截断时整数部分经常可能会出现错误,而小数部分相较于整数部分影响甚微,在一些场合下可以忽略不计。
4.浮点数的正确比较方法
1.问题再现
虽然通过我们的修改程序成功输出了hehe,但是我们的问题真的解决了吗?上例我们只是将两个以字面值出现的浮点数进行比较,但如果出现的是一个表达式呢?我们来看以下代码:
#include<stdio.h>
int main()
{
double t1 = 1.0;
double t2= 0.1;
if (t2 == t1-0.9)
{
printf("hehe");
}
else
printf("haha");
return 0;
}
按照我们之前的分析,等号两边的类型和值一样,因此会打印hehe。但是结果不以为然,运行程序,程序打印haha:
为什么会出现错误呢?其实原因依旧逃不开精度(万恶之源)。我们通过上面分析知道浮点数存储时会出现精度丢失的问题,例如t2实际存储的并非严格的0.1,而是一个极为接近0.1的数。并且,在t1-0.9这个表达式中,0.9也是存在精度损失的,最终计算的结果也并非是0.1。因此,最后二者不等,输出haha。我们也可以通过编写程序来验证:
#include<stdio.h>
int main()
{
double t1 = 1.0;
double t2= 0.1;
printf("%.50lf\n", t1 );
printf("%.50lf\n", t2);
printf("%.50lf\n", t1 - 0.9);
return 0;
}
我们可以直观的看到,由于t1小数部分为0,因此不存在精度损失的问题。但是t2和t1-0.9就不一样了由于精度损失的问题,t2存储的内容并非为0.1,t1-0.9的结果也并非为0.1。我们可以看出,二者尽管非常接近0.1,但还是略有不同,依旧逃离不了编译器的法眼。
2.最终解决方案
"天啊,这也太折磨人了吧",看到这里,会不会有人发出感叹呢(好奇^v^)。由上分析,我们得出的结论是我们不能单纯用==来进行浮点运算的比较。我们发现t2和t1-0.9的差距十分的小,因此我们可以设定一个非常小的阈值,通过判断二者差的绝对值是否小于阈值来判断二者是否相等。其中,对于阈值,在float.h头文件中有定义,当然我们也可以自己设定阈值。
#include<stdio.h>
#include<float.h>
#include<math.h>
int main()
{
double t1 = 1.0;
double t2= 0.1;
//错误的,不能直接比较
//if((t1-0.9)==t2)
//{
// printf("hehe");
//}
//正确做法,通过阈值比较,DBL_EPSILON为浮点数阈值,使得1.0加上后不等于1.0的最小值
if (fabs((t1 - 0.9) - t2) < DBL_EPSILON)
{
printf("hehe");
}
else
{
printf("haha");
}
return 0;
}
对于float.h定义的阈值,其定义是使得1.0加上后的值不等于1.0的最小值。换句话说就是两个数差值如果小于这个最小值,两个数就相等。我们可以由此进行浮点数的比较。
以上,就是本期的所有内容了
制作不易,能否点个赞再走呢!
病酒红尘: 正无穷取整是ceil吧,红字部分打错了
Lalo666666: 优质文章,很用心。
小王毕业啦: 博主的这篇文章对STL中string的用法进行了详细解析,让我对这个话题有了更深入的了解。文章中的细节描写非常到位,让我感受到了博主的专业知识和深厚功底。期待博主未来能够持续分享更多类似的好文,同时也希望能够得到博主的指导,一起共同进步。非常感谢博主的用心分享和支持!
忆梦初心: call指令执行的过程是先将返回地址压入栈中,然后再跳转。
pjk219572421: 这一段没看明白。你的图里面没看到哪一行汇编指令是将00EE18F7压到栈里面: 点击F11进入函数,我们可以发现函数返回后的指令地址被压入栈中(即00EE18F7),然后修改eip进行跳转,转入Add()函数: