【C++】float / double 与 0 值比较
1. 概述不同
当然使用普通的比较没有问题,如果不考虑精度的话,可以使用
double dvalue = 0.0;
if (0.0 == dvalue)
但是,在某些情况下可能出错。
1.1 - float 与 double 实际存储
float
与 double
在计算机中存储的内容可能与想象中等于代码赋予的字面值不同,如下
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 。
注:必须包含 \ 头文件,Windows 上创建 Visual Studio 工程,在它的外部依赖项中包含了自身的相似的 VC 头文件,但 Linux 下不显式包含会报错。
但我们不必自己去定义这样一个数,可以包含 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 如下图
在具体程序中,我们可以定义两个常量,避免函数重复调用,引起性能损失。
#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;
}
}