3

我想知道将浮点数与小数点后三位比较的最快方法是什么。假设我有这样的东西

float lhs = 2.567xxxx
float rhs = 2.566xxxx

以上应该是不同的,如果它是这样的

float lhs = 2.566xxxx
float rhs = 2.566xxxx

他们应该是一样的

更新:

我正在尝试以下

double trunc(double d)
{
    return (d>0) ? floor(d) : ceil(d) ; 
}


bool comparedigits(float a , float b)
{
    if (trunc(1000.0 * a) == trunc(1000.0 * b))
    {
        return true;
    }
    return false;
}

    float g = 2.346;
    float h= 2.34599;
    bool t = comparedigits(g,h) ; //Not the same and should return false;

然而它正在返回真实。

4

5 回答 5

14

为了制止由于允许四舍五入改变结果而导致错误答案的冲击,这里有一个没有四舍五入问题的答案,因为它double用于算术:

trunc(1000. * lhs) == trunc(1000. * rhs);

这是有效的,因为1000.有 type double,所以另一个操作数从 转换floatdouble,并且以double格式执行乘法。1000 与任何float值的乘积都可以在 中精确表示double,因此不存在舍入误差(假设 IEEE 754 32 位和 64 位二进制浮点)。然后我们trunc用来比较小数点后(原始)第三位的数字。

我犹豫要不要提供这个答案,因为我不确定这是 OP 真正想要的。通常,当人们带着“到小数点后三位”比较的要求来到 Stack Overflow 时,他们并没有完全考虑到这个问题。一个完整的正确答案可能要等到我们澄清。

此外,以上仅适用于正数。如果这些值可能是负数,则应对它们的符号进行事先测试,false如果它们不同,则应返回。(否则,–.0009 将报告为等于 +.0009。)

于 2013-09-08T18:04:11.567 回答
5

对于可以适合整数的浮点值,x1000您可以尝试:

if (static_cast<int>(lhs*1000.0) == static_cast<int>(rhs*1000.0))
{
   // Values are near
}
else
{
   // They are not identical (maybe!)
}

在表示浮点值时要注意计算机的准确性。


重要更新

总是有一些数字会导致代码失败,Eric Postpischil 的代码与此代码一样失败。

即使转换为字符串也无济于事,我们可以找到无法正确转换为字符串的数字。

那么,解决方案是什么?这很容易,我们必须定义程序的范围和所需的准确性。在计算机世界中,我们不可能有无限的精度。每个计算机科学家都应该知道的关于浮点运算的知识

于 2013-09-08T16:58:47.953 回答
2

如果我们假设xxxx您语句中的 s 是真的 don't care,即您只关心 7 个小数位的精度,那么以下方案将起作用。

为了处理由于 的精度有限而导致的浮点表示效果float,您可以将参数提升为 double,四舍五入到小数点后 7,然后乘以 1000。然后,您可以使用modf()提取整数部分并进行比较。

bool equals_by_3_decimal_places (float a, float b) {
    double ai, bi;

    modf((.00000005 + a) * 1000, &ai);
    modf((.00000005 + b) * 1000, &bi);
    return ai == bi;
}
于 2013-09-08T17:20:28.817 回答
1

float值转换为具有完整位数 ( std::numeric_limits<float>::dgits10) 的字符串,然后将字符串截断到小数点后 3 位,并比较结果字符串:

std::string convert(float value, int places) {
    if (value == 0) {
        return "0";
    }
    int digits(std::numeric_limits<float>::digits10 - std::log(value) / std::log(10));
    digits = std::max(0, digits);
    std::ostringstream out;
    out << std::fixed << std::setprecision(digits) << value;
    std::string rc(out.str());
    return places < digits? rc.substr(0, rc.size() - (digits - places)): rc;
}

bool compare(float f1, float f2) {
    return convert(f1, 3) == convert(f2, 3);
}

建议乘以 100 或 1000 的各种比较不起作用,因为它们将进行二进制而不是十进制舍入。您可以尝试在相乘之后和截断之前添加 0.5,int但在某些情况下(尽管很少)这种方法仍然失败。但是,只要您最终得到的数字不超过std::numeric_limits<float>::digit10数字,上面的转换就会做正确的事情。尝试处理比这个数字更多的十进制数字将失败,因为float无论如何都不能正确表示尽可能多的十进制数字。

于 2013-09-08T17:03:56.997 回答
-1

1)您正在尝试与浮点进行等于比较。一些浮点格式可以工作,但 IEEE 格式不能工作。你不能做等于比较。您需要将该浮点数转换为 int 然后进行 int 比较。使用整数(不限于 32 位或此处的任何内容)只有一种表示每个数字的方法,因此您可以进行等于比较。

2)记住浮点数学是以2为底的,而你要求做以10为底的东西。所以会有转换问题,截断。此外,我再次假设您使用的是 IEEE,这意味着您具有三种舍入模式(以 2 为底),因此您也必须处理它。你会想做某种 double_to_integer((double*1000.0)+0.5) 并比较它们。如果您发现不起作用的极端案例,我不会感到惊讶。

有关此问题的更多有趣信息。请注意,C 标准不支持以这种方式使用联合,但通常恰好可以工作......

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

double trunc(double d)
{
    return (d>0) ? floor(d) : ceil(d) ;
}
int comparedigits(float a , float b)
{
    if (trunc(1000.0 * a) == trunc(1000.0 * b))
    {
        return 1;
    }
    return 0;
}
union
{
    unsigned int ul;
    float f;
} fun;
union
{
    unsigned int ul[2];
    double d;
} dun;
int main ( void )
{
    float g;
    float h;
    int t;


    g = 2.346;
    h = 2.34599;
    t = comparedigits(g,h);

    printf("%u\n",t);
    printf("raw\n");
    fun.f=g; printf("0x%08X\n",fun.ul);
    fun.f=h; printf("0x%08X\n",fun.ul);
    dun.d=g; printf("0x%08X_%08X\n",dun.ul[1],dun.ul[0]);
    dun.d=h; printf("0x%08X_%08X\n",dun.ul[1],dun.ul[0]);
    printf("trunc\n");
    dun.d=trunc(1000.0 * g); printf("0x%08X_%08X\n",dun.ul[1],dun.ul[0]);
    dun.d=trunc(1000.0 * h); printf("0x%08X_%08X\n",dun.ul[1],dun.ul[0]);
    printf("trunc\n");
    dun.d=trunc(1000.0F * g); printf("0x%08X_%08X\n",dun.ul[1],dun.ul[0]);
    dun.d=trunc(1000.0F * h); printf("0x%08X_%08X\n",dun.ul[1],dun.ul[0]);
    printf("floor\n");
    dun.d=floor(1000.0 * g); printf("0x%08X_%08X\n",dun.ul[1],dun.ul[0]);
    dun.d=floor(1000.0 * h); printf("0x%08X_%08X\n",dun.ul[1],dun.ul[0]);
    printf("ceil\n");
    dun.d=ceil(1000.0 * g); printf("0x%08X_%08X\n",dun.ul[1],dun.ul[0]);
    dun.d=ceil(1000.0 * h); printf("0x%08X_%08X\n",dun.ul[1],dun.ul[0]);

    printf("%u\n",(unsigned int)(g*1000.0));
    printf("%u\n",(unsigned int)(h*1000.0));

    if (trunc(1000.0F * g) == trunc(1000.0F * h))
    {
        printf("true\n");
    }
    else
    {
        printf("false\n");
    }
    return(0);
}

编译运行

gcc test.c -o test -lm
./test 
1
raw
0x401624DD
0x401624B3
0x4002C49B_A0000000
0x4002C496_60000000
trunc
0x40A25200_00000000
0x40A25200_00000000
trunc
0x40A25400_00000000
0x40A25200_00000000
floor
0x40A25200_00000000
0x40A25200_00000000
ceil
0x40A25400_00000000
0x40A25400_00000000
2345
2345
false

所以在单数学而不是双数学中做 1000 * x 似乎可以解决问题

1000.0 * a 是混合模式。除非指定为单,否则 1000.0 是 C 标准的双精度。并且 a 是单数,因此 a 被转换为双数,数学以双数形式完成,然后将其馈送到双精度函数。1000.0F 是单数,a 是单数,所以乘以单数运算,然后转换为双数。所以也许真正的问题在于 g 和 h 的转换和舍入为双精度数。将不得不更多地挖掘尾数差异......

我觉得关键是这个,双倍单1000.0*x的结果

trunc
0x40A25200_00000000
0x40A25200_00000000

如果它们相等,那么您执行相同数量的任何操作都会得到相同的结果。当它是单倍单然后转换为双倍时,它们是不同的。

trunc
0x40A25400_00000000
0x40A25200_00000000

这使您的代码工作(对于这两个特定值)。

false
于 2013-09-08T19:20:34.063 回答