Если оператор
правильно тестируем двух чисел с плавающей точкой на равенство-это то, что многие люди, включая меня, не понимаю. Сегодня, однако, я подумал о том, как некоторые стандартные контейнеры определяют равенство в терминах 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