Ответ
Прямое сравнение чисел с плавающей запятой через == ненадёжно из-за ошибок округления и особенностей представления по стандарту 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, который может помочь в некоторых сравнениях, но универсальной функции в стандарте пока нет.
Ответ 18+ 🔞
О, ну ты держись, сейчас про плавающую точку расскажу так, что сам от себя охуеешь. Смотри, чувак, ты когда пишешь if (0.1 + 0.2 == 0.3), ты думаешь — ну логично же, ёпта! А нихуя! Компьютер тебе в ответ: false, пидарас шерстяной. И волнение ебать начинается: "Как так-то? Я же в школе учился!".
А всё потому, что числа с плавающей запятой — это пиздопроебибна история. Они в памяти хранятся как двоичные дроби, и банальная 0.1 в двоичном виде — это бесконечная дробь, типа нашей 1/3 в десятичной. Компьютер её обрезает, округляет, и получается мелкая, но ошибка. Складываешь — ошибка копится. И вот уже 0.1 + 0.2 — это не 0.3, а 0.30000000000000004. И твой оператор ==, тупой как пробка, смотрит на эти биты и говорит: "Не, братан, разные числа, иди на хуй". Доверия к такому сравнению — ноль ебать.
Так что делать? Нельзя сравнивать напрямую через ==, это путь в никуда, прям вротберунчик. Нужно сравнивать с допуском. То есть говорить: "Если числа отличаются на совсем чуть-чуть, будем считать их одинаковыми".
Вот, смотри, рабочий вариант:
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;
}
Вот это уже серьёзно. absEpsilon ловит случаи, когда оба числа — просто пыль, а 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;
}
Запомни раз и навсегда, ядрёна вошь:
- Фиксированный
epsilon(типа 0.0001) — манда с ушами. Для числа 1000000.0001 и 1000000.0002 он сработает, а для 0.00011 и 0.00012 — уже нет. И наоборот. - Сравнивать с нулём — отдельная песня. Только через абсолютную погрешность (
std::fabs(z) < 1e-15). - NaN и бесконечности (
Inf) — это вообще отдельный цирк. Для них естьstd::isnan()иstd::isinf(),==с ними — это вы ходите по охуенно тонкому льду.
В общем, чувак, мораль проста: никогда не используй == или != для double и float. Никогда. Это прямой билет в мир плавающих багов, которые ты будешь искать неделями. Бери готовую функцию nearlyEqual и живи спокойно. Э бошка думай!