浮动vs双重比较

时间:2015-06-12 17:03:37

标签: c

int main(void)
{
    float me = 1.1;       
    double you = 1.1;   

    if ( me == you ) {
        printf("I love U");
    } else {
        printf("I hate U");
    }
}

这打印“我讨厌你”。为什么呢?

4 个答案:

答案 0 :(得分:3)

浮点数使用二进制分数。如果将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。

如果比较这些值,首先将float转换为double。但是由于计算机不知道原始的十进制值,它只是用所有0填充新双精度的尾随数字,而双精度值更精确。所以两者都比较不相等。

这是使用花车时常见的陷阱。由于这个和其他原因(例如舍入错误),你不应该使用等于/不等的精确比较),而是使用不同于0的最小值进行远程比较:

#include "float.h"

...
// check for "almost equal"
if ( fabs(fval - dval) <= FLT_EPSILON )
    ...

请注意FLT_EPSILON的用法,它是上述单精度float值的值。另请注意<=,而不是<,因为后者实际上需要完全匹配。)

如果比较两个双打,可以使用DBL_EPSILON,但要小心。

根据中间计算,必须增加公差(不能比epsilon进一步减小公差),因为舍入误差等将总结。对于精度,转换和舍入的错误假设,浮动通常不会宽容。

修改

正如@chux所建议的那样,对于较大的值,这可能无法正常工作,因为您必须根据指数缩放EPSILON。这符合我所说的:浮点比较并不像整数比较那么简单。在比较之前考虑一下。

答案 1 :(得分:1)

简而言之,您不应使用==来比较浮点数。

例如

float i = 1.1; //或加倍

float j = 1.1; //或加倍

这个论点  (i==j) == true //并非总是有效

为了正确比较,你应该使用epsilon(非常小的数字):

(abs(i-j)<epsilon)== true //此参数有效

答案 2 :(得分:1)

问题简化了为什么meyou有不同的值?

通常,C浮点基于二进制表示。许多编译器和硬件遵循IEEE 754 binary32binary64。稀有机器使用十进制,base-16或其他浮点表示。

OP的机器当然不代表1.1与1.1完全相同,而是表示最接近的可表示浮点数。

考虑下面的内容,以高精度打印出meyou。还显示了先前可表示的浮点数。很容易看到me != you

#include <math.h>
#include <stdio.h>
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..对此有一个很好的答案。

答案 3 :(得分:0)

因为您正在比较两个浮点数!

由于Rounding Errors,浮点比较并不准确。使用二进制浮点数不能精确表示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(a-b) < 0.0001) // wrong - don't do this

这是一种不好的方法,因为选择固定的epsilon(0.0001)因为它“看起来很小”,当被比较的数字非常小时实际上可能太大了。

我个人使用以下方法,可能会对您有所帮助:

#include <iostream>     // std::cout
#include <cmath>        // std::abs
#include <algorithm>    // 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;
    }
}

此方法passes针对不同的abepsilon测试许多重要的特殊情况。

不要忘记阅读What Every Computer Scientist Should Know About Floating-Point Arithmetic