V550. An odd precise comparison. It's probably better to use a comparison with defined precision: fabs(A - B) < Epsilon or fabs(A - B) > Epsilon.


Анализатор обнаружил потенциальную ошибку связанную с тем, что для сравнения чисел с плавающей точкой используется оператор == или !=. Нередко точное сравнение может быть источником ошибки.

Рассмотрим пример:

double a = 0.5;
if (a == 0.5) //OK
  x++;

double b = sin(M_PI / 6.0);
if (b == 0.5) //ERROR
  x++;

Первое сравнение 'a == 0.5' истинно. Второе сравнение 'b == 0.5' может быть как истинно, так и ложно. Результат выражения 'b == 0.5' зависит от используемого процессора, версии и настроек компилятора. Например, значение переменной 'b' было равно 0.49999999999999994 при использовании компилятора Visual C++ 2010. Более корректно этот код можно написать следующим образом:

double b = sin(M_PI / 6.0);
if (fabs(b - 0.5) < DBL_EPSILON)
  x++;

В данном случае сравнение с погрешностью DBL_EPSILON верно, так как результат функции sin() лежит в диапазоне [-1, 1]. Однако, если мы работаем со значениями больше нескольких единиц, то такие погрешности как FLT_EPSILON, DBL_EPSILON будут слишком малы. И наоборот при работе со значениями типа 0.00001 эти погрешности слишком велики. Каждый раз следует выбирать погрешность адекватную диапазону возможных значений.

Возникает вопрос. Как же все-таки сравнить две переменных типа double?

double a = ...;
double b = ...;
if (a == b) // how?
{
}

Одного единственно-правильного ответа нет. В большинстве случаев можно сравнит две переменных типа double, написав код следующего вида:

if (fabs(a-b) <= DBL_EPSILON * fmax(fabs(a), fabs(b)))
{
}

Только осторожней с этой формулой, она работает только для чисел с одинаковым знаком. Также в ряде с большим количеством вычислений постоянно набегает ошибка, и константа DBL_EPSILON может оказаться слишком маленьким значением.

А можно ли все-таки точно сравнивать значения в формате с плавающей точкой?

В некоторых случаях да. Но эти ситуации весьма ограничены. Сравнивать можно в том случае, если это и есть по сути одно и то же значение.

Пример, где допустимо точное сравнение:

// -1 - значение не инициализировано.
float val = -1.0f;
if (Foo1())
  val = 123.0f;
if (val == -1.0f) //OK
{
}

В данном случае сравнение со значением "-1" допустимо, так как именно точно таким же значением мы инициализировали переменную ранее.

В рамках документации невозможно более подробно раскрыть тему сравнения float/double типов, поэтому мы отсылаем вас к внешнем источникам информации приведенных в конце.

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

Диагностическое сообщение не выдается, если сравниваются два идентичных выражения типа float или double. Такое сравнение позволяет определить, является ли значение NaN. Пример кода, реализующего подобную проверку:

bool isnan(double X) { return X != X; }

Дополнительные ресурсы:

Согласно Common Weakness Enumeration, потенциальные ошибки, найденные с помощью этой диагностики, классифицируются как CWE-682.

Взгляните на примеры ошибок, обнаруженных с помощью диагностики V550.


Найденные ошибки

Проверено проектов
355
Собрано ошибок
13 303

А ты совершаешь ошибки в коде?

Проверь с помощью
PVS-Studio

Статический анализ
кода для C, C++, C#
и Java

goto PVS-Studio;