浮动vs双重比较
int main(void) { float me = 1.1; double you = 1.1; if ( me == you ) { printf("I love U"); } else { printf("I hate U"); } }
这打印“我讨厌你”。 为什么?
浮点数使用二进制分数。 如果将1.1
转换为float,则会产生二进制表示。 如果二进制点将数字的权重减半,那么每个位都是正确的,与十进制一样多,它除以十。 该点左侧的比特加倍(十进制的十倍)。
in decimal: ... 0*2 + 1*1 + 0*0.5 + 0*0.25 + 0*0.125 + 1*0.0625 + ... binary: 0 1 . 0 0 0 1 ... 2's exp: 1 0 -1 -2 -3 -4 (exponent to the power of 2)
问题是1.1
无法精确转换为二进制表示。 但是,对于double,有一些数字比浮点数更多。
如果比较这些值,则首先将float转换为double。 但是由于计算机不知道原始的十进制值,它只是用全0
来填充新double的尾随数字,而double值更精确。 所以两者都比较不相等。
这是使用浮子时常见的陷阱。 由于这个和其他原因(例如舍入误差),你不应该使用等于/不等的精确比较),而是使用不同于0的最小值进行范围比较:
#include "float.h" ... // check for "almost equal" if ( fabs(fval - dval) <= FLT_EPSILON ) ...
请注意FLT_EPSILON的用法,它是上述单精度float
值的值。 另请注意<=
,而不是<
,因为后者实际上需要精确匹配)。
如果你比较两个双打,你可能会使用DBL_EPSILON,但要小心。
根据中间计算,必须增加公差(不能比epsilon进一步减小公差),因为舍入误差等将总结。 对于精度,转换和舍入的错误假设,浮动通常不会宽容。
编辑:
正如@chux所建议的那样,对于较大的值,这可能无法正常工作,因为您必须根据指数扩展EPSILON。 这符合我所说的:浮点比较并不像整数比较那么简单。 在比较之前考虑一下。
简而言之,您不应该使用==
来比较浮点数。
例如
float i = 1.1;
//或加倍
float j = 1.1;
//或加倍
这个参数(i==j) == true
//并不总是有效的
为了正确比较,你应该使用epsilon(非常小的数字):
(abs(ij)
这个问题简化了为什么me
和you
有不同的价值观?
通常 ,C浮点基于二进制表示。 许多编译器和硬件遵循IEEE 754 binary32和binary64 。 稀有机器使用十进制,base-16或其他浮点表示。
OP的机器当然不代表1.1与1.1完全相同,而是表示最接近的可表示浮点数。
考虑下面打印出me
和you
高精度。 还显示了先前可表示的浮点数。 很容易看到me != you
。
#include #include int main(void) { float me = 1.1; double you = 1.1; printf("%.50f\n", nextafterf(me,0)); // previous float value printf("%.50f\n", me); printf("%.50f\n", nextafter(you,0)); // previous double value printf("%.50f\n", you); 1.09999990463256835937500000000000000000000000000000 1.10000002384185791015625000000000000000000000000000 1.09999999999999986677323704498121514916420000000000 1.10000000000000008881784197001252323389053300000000
但它更复杂:C允许代码根据FLT_EVAL_METHOD
使用更高的精度进行中间计算。 所以在另一台机器上, FLT_EVAL_METHOD==1
(将所有FP计算为double
),比较测试可能会通过。
除了与0.0的比较之外,在浮点代码中很少使用比较精确相等。 更常见的是,代码使用有序比较a < b
。 比较近似相等涉及另一个控制接近程度的参数。 @R ..对此有一个很好的答案。
因为你正在比较两个浮点!
由于舍入误差,浮点比较不准确。 使用二进制浮点数不能精确表示1.1或9.0等简单值,浮点数的精度有限意味着操作顺序的微小变化可能会改变结果。 不同的编译器和CPU架构以不同的精度存储临时结果,因此结果将根据环境的详细信息而有所不同。 例如:
float a = 9.0 + 16.0 double b = 25.0 if(a == b) // can be false! if(a >= b) // can also be false!
甚至
if(abs(ab) < 0.0001) // wrong - don't do this
这是一种不好的方法,因为选择固定的epsilon(0.0001)因为它“看起来很小”,当被比较的数字非常小时实际上可能太大了。
我个人使用以下方法,可能会对您有所帮助:
#include // std::cout #include // std::abs #include // std::min using namespace std; #define MIN_NORMAL 1.17549435E-38f #define MAX_VALUE 3.4028235E38f bool nearlyEqual(float a, float b, float epsilon) { float absA = std::abs(a); float absB = std::abs(b); float diff = std::abs(a - b); if (a == b) { return true; } else if (a == 0 || b == 0 || diff < MIN_NORMAL) { return diff < (epsilon * MIN_NORMAL); } else { return diff / std::min(absA + absB, MAX_VALUE) < epsilon; } }
对于不同的a
, b
和epsilon
,此方法通过了许多重要特殊情况的测试。
并且不要忘记阅读每个计算机科学家应该知道的关于浮点运算的内容 !