Ответ
Прямое сравнение чисел с плавающей запятой через == ненадёжно из-за ошибок округления и особенностей представления по стандарту IEEE 754. Необходимо сравнивать с учётом допустимой погрешности (эпсилон).
Рекомендуемый подход — сравнение с относительной погрешностью:
#include <cmath>
#include <algorithm>
#include <limits>
bool approximatelyEqual(double a, double b, double epsilon = 1e-8) {
// Сравниваем абсолютную разницу для чисел, близких к нулю
double absDiff = std::fabs(a - b);
if (absDiff <= epsilon) {
return true;
}
// Используем относительную погрешность для остальных случаев
double maxVal = std::max(std::fabs(a), std::fabs(b));
return absDiff <= epsilon * maxVal;
}
Более надёжная функция, учитывающая краевые случаи:
bool nearlyEqual(double a, double b, double absEpsilon = 1e-12, double relEpsilon = 1e-8) {
double diff = std::fabs(a - b);
if (diff <= absEpsilon) {
return true; // Числа практически идентичны или оба очень близки к нулю
}
// Используем относительную погрешность, основанную на большем из чисел
double larger = std::max(std::fabs(a), std::fabs(b));
return diff <= larger * relEpsilon;
}
// Пример использования
int main() {
double x = 0.1 + 0.2;
double y = 0.3;
if (nearlyEqual(x, y)) {
std::cout << "Numbers are considered equal.n";
}
// Сравнение с нулём требует особого подхода
double z = 1e-20;
if (std::fabs(z) < 1e-15) { // Используем абсолютную погрешность
std::cout << "z is effectively zero.n";
}
return 0;
}
Ключевые моменты:
- Абсолютный эпсилон (
absEpsilon) полезен для сравнения чисел, близких к нулю. - Относительный эпсилон (
relEpsilon) масштабируется с величиной чисел, что делает его универсальным. - Не используйте фиксированный эпсилон (например,
1e-5) для всех диапазонов чисел. - Для специальных значений (
NaN,Inf) используйтеstd::isnan()иstd::isinf(). - В C++20 появился
std::lerp, который может помочь в некоторых сравнениях, но универсальной функции в стандарте пока нет.