首页 > 代码库 > 刨根问底:C++中浮点型变量(float, double)的比较问题。

刨根问底:C++中浮点型变量(float, double)的比较问题。

首先,让我们先来看一段代码:


#include <iostream>
#include <iomanip>

int main()
{
	using namespace std;
	cout<<setprecision(17);
	float num1 = 1.1;
	double num2 = 1.1;
	if (num1 == num2)
		cout << "yes"<<endl;
	else
		cout << "no"<<endl;
	
	cout<<num1<<";"<<num2<<endl;
	return 0;
}


代码很简单,比较下num1和num2是否相等,那么是否相等呢?看字面值是一样的,理论上确实应该相等,但实际上却不是。为什么?这里涉及到了一个浮点型数据的精度和表示问题。


好,我们就来谈谈这两个问题。


1. 精度(Precision)

精度可以理解成在不丢失数据的前提下,可以存储多少位数据的能力。比如,1/3这个分数,如果用小数表示应该是0.333333...的无线循环,因此若想在内存中表示这个数据,需要无限的存储空间,这显然是不可能的。我们常见的float和double型分别是4 bytes和8 bytes,都是只能存储有限位的小数,float和double分别能表示6-7个小数位和15-16个小数位。


2. 进位(Rounding)

这里的进位问题是值10进制转化成2进制的过程。我们都知道十进制整数转化成二进制的方法是不断的除以2,直到除尽为止,而十进制小数转化成二进制则是不断的乘以2,直到值为1为止。这里面有个问题,整数不断的除以2,总有最后为0的时候,但小数乘以2,却不一定得到1,也就是说,并不是所有十进制小数转成有限的二进制数据,而因为上面提到的精度问题,因此,会出现精度缺失问题,这种情况,我们称之为“进位错误”,英文是Rounding error。


基于以上两点,上面那段代码就不难理解了。


num1和num2虽然字面值一样,但是在内存中的存储数据完全不同,在存储1.1的过程中,精度缺失和进位错误同时出现,因此num1和num2做==比较的时候,遵循精度保留原则,num1会被编译器premote成double类型,但由于本身精度是7位小数,强制提升为15位小数后,后面补位的数字都是随机的,这点可以在最后的输出中可以看到(为了以便观察,输出的精度设了17位)。


那么,如何解决这种问题?


1. 可以设定一个可接受的误差值,当误差小到一定程度的时候,就可以忽略了。譬如,abs(num1-num2)<0.00001。

2. 可以给高精度类型强制降精度,比如用这种方式:num1 == static_cast<float>(num2)。但注意,不要给低精度类型强制提升精度,如果这样就等于和编译器做的一样了,结果也是一样的。