将浮点数与三位小数进行比较

时间:2013-09-08 16:55:35

标签: c++ floating-point

我想知道将浮点数比较到三位小数的最快方法。我有类似的东西

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;

然而它正在回归真实。

5 个答案:

答案 0 :(得分:9)

为了阻止错误的答案的冲击,因为它们允许舍入来改变结果,这里是一个没有舍入问题的答案,因为它使用double进行算术:

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

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

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

此外,以上仅适用于正数。如果值可能为负,则应对其符号执行先前测试,如果它们不同,则应返回false。 (否则,-。0009将被报告为等于+.0009。)

答案 1 :(得分:6)

对于可以在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的代码与此代码的代码相同。

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

嗯,解决方案是什么?这很容易,我们必须定义我们程序的范围和所需的准确性。我们不能在计算机世界中拥有无限的精确度。 What Every Computer Scientist Should Know About Floating-Point Arithmetic

答案 2 :(得分:2)

如果我们假设你的陈述中的xxxx是真的不关心,那就是你只关心7位小数的​​精度,那么下面的方案就可以了。

由于float的精度有限,要处理浮点表示效果,可以将参数提升为double,舍入到7 th 小数位,然后乘以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;
}

答案 3 :(得分: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不起作用,因为它们将进行二进制而不是十进制舍入。您可以尝试在乘法之后和截断到int之前添加0.5,但是有些情况(尽管很少)这种方法仍然失败。但是,只要您的结果不超过std::numeric_limits<float>::digit10位数,上述转换就是正确的。尝试处理比此数字更多的十进制数字将失败,因为float无法正确表示正确的十进制数字。

答案 4 :(得分:0)

1)你试图用浮点数做等于比较。一些浮点格式可以工作,但IEEE格式不起作用。你不能做等于比较。您需要将该float转换为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被转换为double,数学运算为double,然后将其转换为double函数。 1000.0F是单个,a是单个,所以乘法是单个数学,然后它被转换为double。所以也许真正的问题在于将g和h转换和舍入为double。将不得不深入挖掘尾数差异......

我认为关键是这个,双倍单次1000.0 * x结果

trunc
0x40A25200_00000000
0x40A25200_00000000

如果它们相等,那么你做同样数字的任何东西都会相同。当它是单次然后转换为双倍时,它们会有所不同。

trunc
0x40A25400_00000000
0x40A25200_00000000

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

false