← Назад к вопросам

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

1.0 Junior🔥 111 комментариев
#Язык C++

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Сравнение вещественных переменных

Сравнение вещественных чисел (floating-point) — одна из самых коварных операций в программировании C/C++. Прямое использование == или != часто приводит к ошибкам из-за ошибок округления и представления чисел в памяти. Вот правильные подходы.

Проблема прямого сравнения

Вещественные числа хранятся в формате IEEE 754, что может привести к накоплению ошибок:

float a = 0.1f + 0.2f;  // = 0.30000002 (приблизительно)
float b = 0.3f;         // = 0.29999999 (приблизительно)

if (a == b) {           // false, хотя ожидаем true!
    std::cout << "Равны";
}

Способ 1: Сравнение с эпсилон (epsilon)

Наиболее популярный и надежный метод:

const float EPSILON = 1e-6f;

bool areEqual(float a, float b) {
    return std::abs(a - b) < EPSILON;
}

bool areLess(float a, float b) {
    return (b - a) > EPSILON;
}

bool areGreater(float a, float b) {
    return (a - b) > EPSILON;
}

bool areLessOrEqual(float a, float b) {
    return areEqual(a, b) || areLess(a, b);
}

Использование:

float x = 0.1f + 0.2f;
float y = 0.3f;

if (areEqual(x, y)) {
    std::cout << "Примерно равны";
}

Способ 2: Относительное сравнение (relative epsilon)

Для больших чисел абсолютное эпсилон может быть неточным. Используй относительное:

bool areEqualRelative(float a, float b, float tolerance = 1e-6f) {
    float maxValue = std::max(std::abs(a), std::abs(b));
    return std::abs(a - b) <= tolerance * maxValue;
}

Пример:

float large1 = 1e6f + 0.1f;
float large2 = 1e6f;

if (areEqualRelative(large1, large2)) {
    std::cout << "Примерно равны";
}

Способ 3: Сравнение на уровне битов (для double)

Для большей точности используй double и специальную функцию:

bool areEqualDouble(double a, double b, double tolerance = 1e-14) {
    return std::abs(a - b) <= tolerance * std::max(1.0, std::max(std::abs(a), std::abs(b)));
}

Способ 4: Использование std::nextafter

Проверка, насколько близки числа на уровне машинного представления:

bool areEqualULP(float a, float b, int maxULP = 4) {
    if (std::signbit(a) != std::signbit(b)) {
        return a == b;  // Разные знаки
    }
    
    float diff = std::abs(a - b);
    for (int i = 0; i < maxULP; ++i) {
        if (a == b) return true;
        a = std::nextafter(a, b);
    }
    return false;
}

Способ 5: Для критически важных вычислений

Используй типизированный шаблон для разных типов:

template<typename T>
bool isEqual(T a, T b, T epsilon = std::numeric_limits<T>::epsilon()) {
    return std::abs(a - b) <= epsilon * std::max({1.0, std::abs(a), std::abs(b)});
}

// Использование
float f1 = 1.0f, f2 = 1.0f;
double d1 = 1.0, d2 = 1.0;

if (isEqual(f1, f2)) std::cout << "float равны";
if (isEqual(d1, d2)) std::cout << "double равны";

Рекомендации

  • Для обычных случаев: используй абсолютное эпсилон (1e-6 для float, 1e-14 для double)
  • Для больших чисел: используй относительное эпсилон
  • Для критичного кода: используй double вместо float
  • Избегай: прямого сравнения с ==, цепочек вычислений с float
  • Помни: о порядке операций — (a + b) + c может отличаться от a + (b + c)

Этот подход гарантирует корректное сравнение вещественных чисел в различных сценариях.