Как сравнить две вещественные переменные в C++?

Ответ

Прямое сравнение вещественных чисел через == ненадежно из-за ошибок округления при представлении в памяти. Стандартный подход — сравнение с допустимой абсолютной погрешностью (epsilon).

#include <cmath>

bool almostEqual(double a, double b, double epsilon = 1e-9) {
    return std::fabs(a - b) < epsilon;
}

Почему этого может быть недостаточно? Абсолютный epsilon плохо работает для чисел разного масштаба (например, сравнение 1e-10 и 2e-10). В таких случаях используют относительную погрешность.

bool relativeEqual(double a, double b, double relEpsilon = 1e-9) {
    double maxVal = std::max(std::fabs(a), std::fabs(b));
    // Защита от деления на ноль при сравнении с нулём
    if (maxVal < relEpsilon) return true;
    return std::fabs(a - b) / maxVal < relEpsilon;
}

Практические советы:

  • Для финансовых расчётов используйте целочисленные типы (счёт в копейках/центах) или библиотеки с десятичной арифметикой.
  • В C++11 появились std::nextafter и std::nexttoward для сравнения с учётом соседних представимых чисел.
  • Библиотеки вроде Google Test (ASSERT_NEAR, ASSERT_DOUBLE_EQ) реализуют комбинированный подход (Ульп-тест), проверяющий и абсолютную, и относительную разницу.

Ответ 18+ 🔞

Ёпта, ну смотри, тут такая история с этими вещественными числами — просто пиздец, а не тема. Берёшь два double, сравниваешь через == и думаешь, что всё окей, а потом бац — и нихуя не равно, хотя по логике должно быть. А всё потому, что в памяти они представляются с какими-то ебенями, с ошибками округления, короче, полный ёперный театр.

Вот стандартный костыль, который все используют — сравнение с какой-то погрешностью, эпсилон там.

#include <cmath>

bool almostEqual(double a, double b, double epsilon = 1e-9) {
    return std::fabs(a - b) < epsilon;
}

Но тут, чувак, подвох есть, и он жирный. Этот метод — хуй с горы, если числа разного масштаба. Ну представь: сравниваешь 1e-10 и 2e-10. Разница-то копеечная, но абсолютный эпсилон в 1e-9 её просто не заметит, скажет "равно", а по факту-то одно число в два раза больше другого! Вот это пиздопроебибна, да?

Поэтому умные дядьки придумали сравнивать ещё и относительно, через относительную погрешность.

bool relativeEqual(double a, double b, double relEpsilon = 1e-9) {
    double maxVal = std::max(std::fabs(a), std::fabs(b));
    // Защита от деления на ноль, если оба числа — почти нули
    if (maxVal < relEpsilon) return true;
    return std::fabs(a - b) / maxVal < relEpsilon;
}

А теперь практика, без неё нихуя:

  • Если пишешь что-то для денег — для финансовых расчётов — выкинь нахуй эти double сразу. Бери целые числа, считай в копейках, центах, сатоши, не еби мозг. Или бери спецбиблиотеки для десятичной арифметики, там всё чётко.
  • В C++11, кстати, подвезли std::nextafter и std::nexttoward — ими можно проверять, являются ли числа соседними представимыми значениями, это тоже вариант, но мозг надо включать.
  • Самый, бля, безбашенный и правильный способ — не изобретать велосипед. Возьми Google Test, там есть ASSERT_NEAR и ASSERT_DOUBLE_EQ. Они внутри используют комбинированный подход (этот ваш Ульп-тест), который проверяет и абсолютную, и относительную разницу — и волнение ебать отпадает. Просто пользуйся и не парься.