Подтвердить что ты не робот

Сравнение числа с плавающей точкой с нолем

С++ FAQ lite [29.17] Почему мое сравнение с плавающей запятой не работает? рекомендует этот тест равенства:

#include <cmath>  /* for std::abs(double) */

inline bool isEqual(double x, double y)
{
  const double epsilon = /* some small number such as 1e-5 */;
  return std::abs(x - y) <= epsilon * std::abs(x);
  // see Knuth section 4.2.2 pages 217-218
}
  • Правильно ли это, что это означает, что единственными числами, равными нулю, являются +0 и -0?
  • Следует ли использовать эту функцию также при тестировании нуля или, скорее, теста типа |x| < epsilon?

Обновление

Как отметил Даниэль Даранас, функцию следует, вероятно, лучше называть isNearlyEqual (в этом я забочусь).

Кто-то указал эту ссылку, которую я хочу разделить более заметно.

4b9b3361

Ответ 1

Вы верны своим наблюдениям.

Если x == 0.0, то abs(x) * epsilon равно нулю, и вы проверяете, abs(y) <= 0.0.

Если y == 0.0, то вы тестируете abs(x) <= abs(x) * epsilon, что означает либо epsilon >= 1 (это не так), либо x == 0.0.

Таким образом, либо is_equal(val, 0.0), либо is_equal(0.0, val) будет бессмысленным, и вы можете просто сказать val == 0.0. Если вы хотите принять ровно +0.0 и -0.0.

Часто задаваемые вопросы в этом случае имеют ограниченную полезность. Сравнение "с плавающей запятой" не сравнивается со всеми параметрами "один размер подходит".. Вам нужно подумать о семантике ваших переменных, допустимом диапазоне значений и величине ошибки, введенной вашими вычислениями. Даже в FAQ упоминается оговорка, говоря, что эта функция обычно не является проблемой "когда величины x и y значительно больше, чем epsilon, но ваш пробег может меняться".

Ответ 2

Нет.

Равенство есть равенство.

Эта функция не будет проверять два удвоения для равенства, как его имя promises. Он будет проверять только, если два двойника "достаточно близки" друг к другу.

Если вы действительно хотите проверить два двойника для равенства, используйте этот:

inline bool isEqual(double x, double y)
{
   return x == y;
}

Стандарты кодирования обычно рекомендуют не сравнивать два двойника для точного равенства. Но это другой вопрос. Если вы действительно хотите сравнить два удвоения для точного равенства, x == y - это код, который вы хотите.

10.00000000000000001 не равно 10.0, что бы они ни говорили вам.

пример использования точного равенства - это когда конкретное значение double используется как синоним некоторого специального состояния, такого как "ожидающее калонирование" или "отсутствие данных". Это возможно только в том случае, если фактические числовые значения после этого ожидающего вычисления являются лишь подмножеством возможных значений double. Наиболее типичным частным случаем является то, что это значение является неотрицательным, и вы используете -1.0 в качестве (точного) представления "ожидающего вычисления" или "отсутствия данных". Вы можете представить это с константой:

const double NO_DATA = -1.0;

double myData = getSomeDataWhichIsAlwaysNonNegative(someParameters);

if (myData != -1.0)
{
    ...
}

Я не защищаю это как идеальный дизайн, но тот, который работает. Ключевым моментом является то, что getSomeDataWhichIsAlwaysNonNegative вернет ту же константу, NO_DATA, которая равна -1.0, поэтому ваши два -1.0 будут идентичными.

Ответ 3

Вы можете использовать std::nextafter с фиксированным factor для epsilon значения, подобного следующему:

bool isNearlyEqual(double a, double b)
{
  int factor = /* a fixed factor of epsilon */;

  double min_a = a - (a - std::nextafter(a, std::numeric_limits<double>::lowest())) * factor;
  double max_a = a + (std::nextafter(a, std::numeric_limits<double>::max()) - a) * factor;

  return min_a <= b && max_a >= b;
}

Ответ 4

2 + 2 = 5 (*)

(для некоторых значений с плавающей запятой 2)

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

Итак, хотя мы можем легко представить "1.0, -1.0, 0.1, -0.1" по мере того, как мы доходим до больших чисел, мы начинаем видеть приближения - или мы должны, за исключением того, что мы часто скрываем их, обрезая числа для отображения.

В результате мы можем подумать, что компьютер хранит "0,003", но вместо этого он может хранить "0.0033333333334".

Что произойдет, если вы выполните "0.0003 - 0.0002"? Мы ожидаем .0001, но фактические значения, которые хранятся, могут быть больше похожими на "0.00033" - "0.00029", что дает "0,000004" или самое близкое представляемое значение, которое может быть 0, или оно может быть "0.000006".

С текущими математическими операциями с плавающей запятой не гарантируется, что (a/b) * b == a.

#include <stdio.h>

// defeat inline optimizations of 'a / b * b' to 'a'
extern double bodge(int base, int divisor) {
    return static_cast<double>(base) / static_cast<double>(divisor);
}

int main() {
    int errors = 0;
    for (int b = 1; b < 100; ++b) {
        for (int d = 1; d < 100; ++d) {
            // b / d * d ... should == b
            double res = bodge(b, d) * static_cast<double>(d);
            // but it doesn't always
            if (res != static_cast<double>(b))
                ++errors;
        }
    }
    printf("errors: %d\n", errors);
}

ideone сообщает 599 экземпляров, где (b * d)/d!= b, используя только 10 000 комбинаций из 1 <= b <= 100 и 1 <= d <= 100.

Решение, описанное в FAQ, по существу, должно применять ограничение гранулярности - для тестирования if (a == b +/- epsilon).

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

С++ 11 представил std:: ratio в качестве основы для конверсий с фиксированной точкой между разными единицами времени.

Ответ 5

Как отмечал @Exceptyon, эта функция "относительна" к значениям, которые вы сравниваете. Мера Epsilon * abs(x) будет масштабироваться на основе значения x, так что вы получите результат сравнения так же точно, как epsilon, независимо от диапазона значений в x или y.

Если вы сравниваете ноль (y) с другим действительно маленьким значением (x), скажем, 1e-8, abs(x-y) = 1e-8 будет по-прежнему намного больше, чем epsilon *abs(x) = 1e-13. Поэтому, если вы не имеете дело с чрезвычайно маленьким числом, которое невозможно представить в двойном типе, эта функция должна выполнять задание и будет соответствовать нулю только против +0 и -0.

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

пс: Это аккуратная функция. Спасибо, что указали на это.

Ответ 6

Если вас интересуют только +0.0 и -0.0, вы можете использовать fpclassify из <cmath>. Например:

if( FP_ZERO == fpclassify(x) ) do_something;

Ответ 7

Простое сравнение номеров FP имеет свою специфику, и ключом является понимание формата FP (см. https://en.wikipedia.org/wiki/IEEE_floating_point)

Когда числа FP вычисляются по-другому, одно через sin(), другое, хотя exp(), строгое равенство не будет работать, хотя математически числа могут быть равны. Точно так же не будет работать равенство с константой. Фактически, во многих ситуациях номера FP не должны сравниваться с использованием строгого равенства (==)

В таких случаях следует использовать константу DBL_EPSIPON, которая является минимальным значением не меняет представление 1.0, добавляемое к числу больше 1.0. Для чисел с плавающей запятой не существует всего 2.0 DBL_EPSIPON. Между тем, DBL_EPSILON имеет показатель -16, что означает, что все числа, скажем, с показателем -34, будут абсолютно равны по сравнению с DBL_EPSILON.

Также см. пример, почему 10.0 == 10.0000000000000001

Сравнение чисел dwo с плавающей запятой зависит от характера этих чисел, мы должны рассчитать DBL_EPSILON для них, что было бы значимым для сравнения. Просто мы должны умножить DBL_EPSILON на одно из этих чисел. Какой из них? Максимальный курс

bool close_enough(double a, double b){
    if (fabs(a - b) <= DBL_EPSILON * std::fmax(fabs(a), fabs(b)))
    {
        return true;
    }
    return false;
}

Все остальные способы дадут вам ошибки с неравенством, которые могут быть очень трудно поймать

Ответ 8

этот код:

std::abs((x - y)/x) <= epsilon

вам требуется, чтобы "относительная ошибка" в var была <= epsilon, а не абсолютная разница