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



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

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;

然而,它正在返回真实。

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

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

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

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

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

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

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

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

如果我们假设你的语句中的xxxx s是真的,不关心,也就是说你只关心7位小数的精度,那么下面的方案将起作用。

为了处理由于float精度有限而导致的浮点表示效应,您可以将参数提升为双倍,四舍五入到小数点后 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;
}
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无论如何都无法正确表示尽可能多的十进制数字。

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("%un",t);
    printf("rawn");
    fun.f=g; printf("0x%08Xn",fun.ul);
    fun.f=h; printf("0x%08Xn",fun.ul);
    dun.d=g; printf("0x%08X_%08Xn",dun.ul[1],dun.ul[0]);
    dun.d=h; printf("0x%08X_%08Xn",dun.ul[1],dun.ul[0]);
    printf("truncn");
    dun.d=trunc(1000.0 * g); printf("0x%08X_%08Xn",dun.ul[1],dun.ul[0]);
    dun.d=trunc(1000.0 * h); printf("0x%08X_%08Xn",dun.ul[1],dun.ul[0]);
    printf("truncn");
    dun.d=trunc(1000.0F * g); printf("0x%08X_%08Xn",dun.ul[1],dun.ul[0]);
    dun.d=trunc(1000.0F * h); printf("0x%08X_%08Xn",dun.ul[1],dun.ul[0]);
    printf("floorn");
    dun.d=floor(1000.0 * g); printf("0x%08X_%08Xn",dun.ul[1],dun.ul[0]);
    dun.d=floor(1000.0 * h); printf("0x%08X_%08Xn",dun.ul[1],dun.ul[0]);
    printf("ceiln");
    dun.d=ceil(1000.0 * g); printf("0x%08X_%08Xn",dun.ul[1],dun.ul[0]);
    dun.d=ceil(1000.0 * h); printf("0x%08X_%08Xn",dun.ul[1],dun.ul[0]);
    printf("%un",(unsigned int)(g*1000.0));
    printf("%un",(unsigned int)(h*1000.0));
    if (trunc(1000.0F * g) == trunc(1000.0F * h))
    {
        printf("truen");
    }
    else
    {
        printf("falsen");
    }
    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 为混合模式。 根据 C 标准,1000.0 是双精度,除非指定为单精度。 a 是单精度,所以 a 被转换为双精度,数学被作为双精度完成,然后它被馈送到双精度函数。 1000.0F 是单数,a 是单数,因此乘法作为单数学完成,然后转换为双倍。 因此,也许真正的问题在于将g和h转换为双精度。 将不得不更多地挖掘尾数的差异......

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

trunc
0x40A25200_00000000
0x40A25200_00000000

如果这些是相等的,那么你做的任何相同的数字都会得到相同的结果。 当它是单乘以单,然后转换为双倍时,它们会有所不同。

trunc
0x40A25400_00000000
0x40A25200_00000000

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

false

最新更新