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

в C++ чаво облегченная "[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
}
  1. правильно ли, что это означает, что единственными числами, равными нулю, являются +0 и -0?
  2. следует ли использовать эту функцию также при тестировании на ноль или, скорее, тест, такой как |x| < epsilon?

обновление

как отметил Даниил Daranas функции наверное, лучше назвать isNearlyEqual (что меня волнует).

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

8 ответов


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

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


нет.

равенство-это равенство.

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

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

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

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

10.00000000000000001 не равно 10.0, независимо от того, что они вам говорят.

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

const double NO_DATA = -1.0;

double myData = getSomeDataWhichIsAlwaysNonNegative(someParameters);

if (myData != NO_DATA)
{
    ...
}

можно использовать 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;
}

2 + 2 = 5(*)

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

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

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

что произойдет, если вы выполните "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

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

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

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


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

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


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

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

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

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


простое сравнение чисел 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;
}

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


обратите внимание, что код:

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

вы требуете, чтобы "относительная ошибка" на var была