【C++】float / double 与 0 值比较

1. 概述不同


当然使用普通的比较没有问题,如果不考虑精度的话,可以使用

double dvalue = 0.0;
if (0.0 == dvalue)

但是,在某些情况下可能出错。

1.1 - float 与 double 实际存储


floatdouble 在计算机中存储的内容可能与想象中等于代码赋予的字面值不同,如下

float f = 0.1; // f = 0.100000001490116119384765625
double g = 0.1; // g = 0.1000000000000000055511151231257827021181583404541015625

因此与 0 值的比较不可以单纯比较 == 0.0

1.2 - C 语言与 C++ 中不同


然而不仅两种类型不同,单独 double 类型在 C 语言 / C++ 两种语言中也是不同的,比如如下一段代码:

int main ()
{
	double a = -1.0e-120;
	if (a < 0.0)
		printf ("%g < 0\n", a);
	if (a > 0.0)
		printf ("%g > 0\n", a);
	if (a == 0.0)
		printf ("%g == 0\n", a);
}

使用 gcc 3.3 编译 C 语言,得到的结果为

-1.0e-120 < 0

而使用同一个编译器 编译 C++ ,得到的结果为

-1.0e-120 == 0

2. 比较方法

这里使用 C++ 的方式进行比较

2.1 - C 风格比较


float,double 分别遵循 R32-24 , R64-53 的标准。网上有一些答案判断 float 的精度误差在 ±1e-6 , double 精度误差在 ±1e-15 之间,示例:

#include <cmath>
#include <cstdio>
#define FLOAT_EPSILON 1e-6
#define DOUBLE_EPSILON 1e-15

int main(int argc, char* argv[])
{
	float f = 0.0;
	double d = 0.0;
	
	if (fabs(f) < FLOAT_EPSILON) {
		printf("float value: %g ,",fabs(f));
		printf("is equal to zero!\n");
	} else {
		printf("float value: %g ,", fabs(f));
		printf("is not equal to zero!\n");
	}
	

	if (fabs(d) < DOUBLE_EPSILON) {
		printf("double value: %g ,", fabs(d));
		printf("is equal zero!\n");
	} else {
		printf("double value: %g ,", fabs(d));
		printf("double not equal to zero!\n");
	}
	
	return 0;
}

判断一个单精度浮点数:if (fabs(f) <= 1e-6)
判断一个双精度浮点数:if (fabs(f) <= 1e-15)
若在正负范围内,表示等于 0 ,否则,不为 0 。

但我们不必自己去定义这样一个数,可以包含 C 语言的头文件 float.h

#include <cfloat>

头文件中有定义

// smallest such that 1.0+DBL_EPSILON != 1.0
#define DBL_EPSILON      2.2204460492503131e-016 
// smallest such that 1.0+FLT_EPSILON != 1.0
#define FLT_EPSILON      1.192092896e-07F        

最小的增量使得相等判断失效。

DBL_EPSILON 为影响 double 相等比较的最小增量,DBL 表示 double.
FLT_EPSILON 为影响 float 的最小增量 FLT 表示 float.

另有 LDBL_EPSILON 为影响 long double 的最小增量,实际值在 Windows 上与 DBL_EPSILON 一致。

因此代码可以简化为

#include <cmath>
#include <cstdio>
#include <cfloat>
#include <iostream>

int main(int argc, char* argv[])
{
	float f = 0.0;
	double d = 0.0;
	
	if (fabs(f) < FLT_EPSILON) {
		printf("float value: %g ,",fabs(f));
		std::cout << "is equal to zero!" << std::endl;
	} else {
		printf("float value: %g ,", fabs(f));
		std::cout << "is not equal to zero!" << std::endl;
	}
	

	if (fabs(d) < DBL_EPSILON) {
		printf("double value: %g ,", fabs(d));
		std::cout << "is equal to zero!" << std::endl;
	} else {
		printf("double value: %g ,", fabs(d));
		std::cout << "is not equal to zero!" << std::endl;
	}
	
	return 0;
}

2.2 - 使用 limits 函数


我们可以使用 C++ 标准库中的 <limits> , 使用函数 epsilon 如下图

【C++】float / double 与 0 值比较-LMLPHP

在具体程序中,我们可以定义两个常量,避免函数重复调用,引起性能损失。

#include <iostream>
#include <limits>
#include <cmath> // 一定要包含

// 定义两个常量
constexpr double dbl_eps = std::numeric_limits<double>::epsilon();
constexpr float flt_eps = std::numeric_limits<float>::epsilon();

int main(int argc, char* argv[])
{
	float f = 0.0;
	double d = 0.0;
	
	if (fabs(f) < flt_eps) {
		std::cout << "float value is equal to zero!" << std::endl;
	} else {
		std::cout << "float value is not equal to zero!" << std::endl;
	}
	

	if (fabs(d) < dbl_eps) 
	{
		std::cout << "double value is equal to zero!" << std::endl;
	} 
	else 
	{
		std::cout << "double value is not equal to zero!" << std::endl;
	}
	
	return 0;
}

另外为了使用字符串与浮点数互相转换可以使用以下方法:

浮点数转字符串

#include <sstream>
#include <string>

std::string DoubleToString(const double dvalue, int precision)
{
	std::stringstream ss;
	
	ss.precision(precision);
	ss.setf(std::ios::showpoint, std::ios_base::floatfield); // std::ios::showpoint 为了避免截断,如避免 1.000000000452 转为字符串为 1 的情况
	
	ss << dvalue;
	
	return ss.str();
}

字符串转浮点数

double StringToDouble(const std::string& str)
{
	try 
	{
		return std::stod(str);
	}
	catch(...)
	{
		std::cerr << "Unable to convert string:" << str << endl;
		return 0.0;
	}
}

3. 参考链接 References


07-11 04:23