Если оператор
правильно тестируем двух чисел с плавающей точкой на равенство-это то, что многие люди, включая меня, не понимаю. Сегодня, однако, я подумал о том, как некоторые стандартные контейнеры определяют равенство в терминах 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 находятся в двоичном эквиваленте научной нотации с фиксированным числом значимых битов. Если результат вычисления с бесконечной точностью не является точно представимым, фактический результат является самым близким, который точно представим.
есть две большие ловушки с этим.
- многие простые, короткие десятичные расширения, такие как 0.1, не совсем представимы в float или double.
- два результата, которые быть равным в арифметике действительных чисел может отличаться в арифметике с плавающей запятой. Например, арифметика с плавающей запятой не является ассоциативной -
(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