Является ли "const" двойное копирование + сравнение безопасным?

Я заметил там много дискуссий по теме ошибок вычисления с плавающей запятой, которые требуют более сложного сравнения, чем ==. Тем не менее, все эти статьи, похоже, предполагают, что значение каким-то образом манипулирует (или дважды вычисляется), в то время как я не видел примера, охватывающего очень простое постоянное копирование.

Пожалуйста, обратите внимание на следующее:

const double magical_value = -10;

class Test
{
    double _val;

public:
    Test()
        : _val(magical_value)
    {
    }

    bool is_special()
    {
        return _val == magical_value;
    }
};

Насколько я понимаю, magical_value следует установить во время компиляции, чтобы в этом месте все округление. После этого значение следует просто скопировать в класс и сравнить с исходным. Насколько безопасно такое сравнение? Или может ли копирование или сравнение вводить ошибки здесь?

Пожалуйста, не предлагайте альтернативные методы сравнения или использования магических значений, это другая тема. Мне просто интересно это предположение.

Изменить: просто для того, чтобы заметить, я немного боюсь, что на некоторых архитектурах оптимизация может привести к копированию значения в регистры с плавающей запятой разного размера, что приведет к различиям в точном значении. Есть ли риск чего-то подобного?

Ответ 1

Насколько безопасно такое сравнение? Или может ли копирование или сравнение вводить ошибки здесь?

Да, безопасно (это требование операции копирования, как подразумевается =). Нет конверсий/рекламных акций, о которых вам нужно беспокоиться до тех пор, пока исходные и целевые типы будут такими же.

Однако обратите внимание, что magical_value может не содержать 10 точно, но приближение. Это приближение будет скопировано на _val.

Учитывая квалификатор const, вероятность того, что magical_value, вероятно, будет оптимизирована (если вы включите оптимизацию) или будет использована как есть (то есть, вероятно, память не будет использована).

Ответ 2

Помимо регистров разного размера, у вас есть денормализованная с плавающей запятой (cq flush-to-zero), о которой нужно беспокоиться (см. Почему меняется с 0,1 до 0 замедление производительности на 10x?)

Просто, чтобы дать представление о странности, к которой это может привести, попробуйте этот бит кода:

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: %d\n",SUPPORT_DENORMALIZATION);
printf("a = %.48f, aa = %.48f\na==aa %d, a==0.0f %d, aa==0.0f %d\n",a,aa,a==aa,a==0.0f,aa==0.0f);
printf("b = %.48f, bb = %.48f\nb==bb %d, b==0.0f %d, bb==0.0f %d\n",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 будет true.

Итак, если вы все еще думаете о сравнении значений с плавающей запятой, по крайней мере, избегайте небольших значений.

обновить, чтобы компенсировать некоторые комментарии об этом, из-за использования float вместо double, он также работает в два раза, но вам нужно будет установить константу где-нибудь ниже DBL_MIN, например. 1e-309.

обновить 2 образец кода, относящийся к некоторым комментариям, сделанным ниже. Это показывает, что проблема существует и для парных, и что сравнения могут стать непоследовательными (когда включен сброс до нуля)

    double a;
    const double b = 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001225;
    const double c = 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002225;

    printf("b==c %d\n",b==c);
    a = b;
    printf("assigned a=b: a==b %d\n",a==b);
    a = c;
    printf("assigned a=c: a==b %d\n",a==b);

выход:

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

Проблема отображается в последней строке, где вы наивно ожидаете, что a==b станет ложным после назначения a=c с помощью c!=b.