Если оператор

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

предполагая operator< работает "правильно", в отличие от operator==, Почему мы не могли сделать это:

bool floateq(float a, float b) {
    //check NaN
    return !(a < b) && !(b < a);
}

на самом деле, я провел тест с дополнительной перегрузкой для двойников, как видно здесь, и, похоже, имеют те же подводные камни, что и сравнение их с operator==:

std::cout << "float->double vs double: " 
          << floateq(static_cast<double>(0.7f), 0.7) << " " 
          << (static_cast<double>(0.7f) == 0.7) << "n";

выход:

float - > double vs double: 0 0

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

5 ответов


на ==, <, >, <=, >= и != операторы отлично работают с числами с плавающей запятой.

у вас, кажется, есть предпосылка, что некоторые разумной реализации < должны compare (double)0.7 f равно 0.7. Это не так. Если вы бросаете 0.7f до double вы получаете 0x1.666666p-1. Однако,0.7 равна 0x1.6666666666666p-1. Они не численно равны; на самом деле,(double)0.7f значительно меньше, чем

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


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

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

  • x > y и x >= y оба подразумевают, что числовое количество, которое x предполагается представлять, вероятно, больше, чем y, и в худшем случае, вероятно не меньше, чем y.

  • x < y и x <= y оба подразумевают, что числовое количество, которое x предполагается представлять, вероятно, меньше, чем y, и в худшем случае, вероятно, не намного больше, чем y.

  • x == y подразумевает, что числовые величины, которые x и y представляют неотличимы друг от друга

обратите внимание, что если x типа float, и y типа double, вышеуказанные значения будут достигнуты, если


следующий код (который я изменил, чтобы он компилировался: в частности, вызов floateq было изменено на floatcmp) выводит float->double vs double: 1 0 , а не 0 0 (как и следовало ожидать при сравнении этих двух значений в виде поплавков).

#include <iostream>

bool floatcmp(float a, float b) {
    //check NaN
    return !(a < b) && !(b < a);
}

int main()
{
    std::cout << "float->double vs double: "
              << floatcmp(static_cast<double>(0.7f), 0.7) << " "
              << (static_cast<double>(0.7f) == 0.7) << "\n";
}

однако для стандартной библиотеки важно то, что operator< определяет строгий слабый порядок, который он фактически делает для типов с плавающей запятой.

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


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

есть две большие ловушки с этим.

  1. многие простые, короткие десятичные расширения, такие как 0.1, не совсем представимы в float или double.
  2. два результата, которые быть равным в арифметике действительных чисел может отличаться в арифметике с плавающей запятой. Например, арифметика с плавающей запятой не является ассоциативной - (a + b) + c - Это не обязательно то же самое как a + (b + c)

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

Если нет такого допуска, это означает, что вы использование неправильного типа с плавающей запятой или вообще не должно использоваться с плавающей запятой. 32-разрядный IEEE 754 имеет такую ограниченную точность, что может быть очень сложно найти соответствующий допуск. Как правило, 64-бит-гораздо лучший выбор.


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

еще один пример кода, который показывает, что ваше сравнение не работает (http://ideone.com/mI4S76).

#include <iostream>

bool floatcmp(float a, float b) {
    //check NaN
    return !(a < b) && !(b < a);
}

int main() {
    using namespace std;

    float a = 0.1;
    float b = 0.1;

    // Introducing rounding error:
    b += 1;
    // Just to be sure change is not inlined
    cout << "B after increase = " << b << endl;
    b -= 1;
    cout << "B after decrease = " << b << endl;

    cout << "A " << (floatcmp(a, b) ? "equals" : "is not equal to") << "B" << endl;
}

выход:

B after increase = 1.1
B after decrease = 0.1
A is not equal toB