'const'双重复制+比较安全吗?

Is 'const' double copying + comparison safe?

本文关键字:安全 比较 const 复制      更新时间:2023-10-16

我注意到有很多关于浮点计算错误的讨论,这需要你使用比==更复杂的比较。然而,所有这些文章似乎都假设该值以某种方式纵(或双重计算),而我没有看到一个涵盖非常简单的持续复制的示例。

请考虑以下事项:

const double magical_value = -10;
class Test
{
    double _val;
public:
    Test()
        : _val(magical_value)
    {
    }
    bool is_special()
    {
        return _val == magical_value;
    }
};

据我了解,magical_value应该在编译时设置,以便所有舍入都发生在该点。之后,应将该值复制到类中,并与原始值进行比较。这样的比较保证是安全的吗?或者复制或比较会在这里引入错误吗?

请不要建议其他比较或神奇值使用方法,这是另一个话题。我只是对这个假设感到好奇。

编辑:请注意,我有点担心在某些架构上,优化可能会导致将值复制到不同大小的浮点寄存器,从而引入确切值的差异。有没有发生类似事情的风险?

这样的比较保证是安全的吗?或者复制或比较会在这里引入错误吗?

是的,安全(这是 = 所暗示的复制操作的要求)。只要来源和目标类型相同,就无需担心转化/促销。

但是,请注意,magical_value可能并不完全包含10,而是一个近似值。此近似值将被复制到 _val 中。

考虑到const限定符,magical_value可能会被优化掉(如果您打开优化)或按原样使用(即可能不会用完内存)。

除了可能不同大小的寄存器之外,您还需要担心非规范化浮点(cq 齐平为零)(请参阅为什么将 0.1f 更改为 0 会使性能降低 10 倍?

只是为了说明这可能导致的奇怪之处,请尝试以下代码:

float       a = 0.000000000000000000000000000000000000000047683384;
const float b = 0.000000000000000000000000000000000000000047683384;
float aa = a, bb = b;
#define SUPPORT_DENORMALIZATION ({volatile double t=DBL_MIN/2.0;t!=0.0;})
printf("support denormals: %dn",SUPPORT_DENORMALIZATION);
printf("a = %.48f, aa = %.48fna==aa %d, a==0.0f %d, aa==0.0f %dn",a,aa,a==aa,a==0.0f,aa==0.0f);
printf("b = %.48f, bb = %.48fnb==bb %d, b==0.0f %d, bb==0.0f %dn",b,bb,b==bb,b==0.0f,bb==0.0f);

它给出:(编译时不刷新为零)

support denormals: 1
a = 0.000000000000000000000000000000000000000047683384, aa = 0.000000000000000000000000000000000000000047683384
a==aa 1, a==0.0f 0, aa==0.0f 0
b = 0.000000000000000000000000000000000000000047683384, bb = 0.000000000000000000000000000000000000000047683384
b==bb 1, b==0.0f 0, bb==0.0f 0

或者:(用gcc -ffast-math编译)

support denormals: 0
a = 0.000000000000000000000000000000000000000000000000, aa = 0.000000000000000000000000000000000000000000000000
a==aa 1, a==0.0f 1, aa==0.0f 1
b = 0.000000000000000000000000000000000000000047683384, bb = 0.000000000000000000000000000000000000000000000000
b==bb 1, b==0.0f 0, bb==0.0f 1

当然,最后一行是奇怪的:b==bb && b!=0.0f && bb==0.0f是真的。

因此,如果您仍在考虑比较浮点值,请至少远离较小的值。

更新以抵消由于使用浮点数而不是双精度而导致的一些评论,它也适用于双精度,但您需要将常量设置为低于 DBL_MIN 的某个位置,例如 1e-309 .

更新 2 与下面提出的一些注释相关的代码示例。这表明双精度也存在问题,并且比较可能会变得不一致(当启用刷新为零时)

    double a;
    const double b = 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001225;
    const double c = 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002225;
    printf("b==c %dn",b==c);
    a = b;
    printf("assigned a=b: a==b %dn",a==b);
    a = c;
    printf("assigned a=c: a==b %dn",a==b);

输出:

b==c 0
assigned a=b: a==b 1
assigned a=c: a==b 1

该问题显示在最后一行中,您会天真地期望在分配a=ca==b会变为 c!=b