2013-09-24 2 views
11

Посмотрите на этот код:с ++ сравнение двух двойных значений не работает должным образом

#include <cmath> 
#include <iostream> 
using namespace std; 
class Sphere 
{ 
    double r; 
public: 
    double V() const { return (4/3) * 3.14 * pow(r,3); } 
    bool equal(const Sphere& s) const 
    { 
     cout << V() << " == " << s.V() << " : " << (V() == s.V()); 
     return (V() == s.V()); 

    } 

    explicit Sphere(double rr = 1): r(rr){} 

}; 
main() 
{ 
    Sphere s(3); 
    s.equal(s); 
} 

Выход 84.78 == 84.78 : 0 что означает тот же метод не возвращает то же значение каждый раз, даже если все параметры являются статическими?

Но если я пишу 3.0 вместо 3.14 в определении V() метода, как это:

double V() const { return (4/3) * 3.0 * pow(r,3); } 

Затем выход: 84.78 == 84.78 : 1

Что здесь происходит? Мне нужен этот метод, для моей программы, который будет сравнивать объемы двух объектов, но это невозможно? Я так долго ударил головой, чтобы выяснить, в чем причина проблемы, и, к счастью, я ее нашел, но теперь я не понимаю, почему? Имеет ли он какое-то отношение к компилятору (GCC), или я пропустил что-то важное здесь?

+2

вы просто не проверяете число с плавающей точкой для такого равенства. – yngccc

+0

@yngum почему? как я должен? – tuks

+1

Обычно это плохая идея для проверки значений с плавающей запятой для равенства, поскольку небольшие ошибки округления могут дать неожиданные результаты. Но, как вы говорите, это делает тот же расчет дважды с одним и тем же входом, поэтому тест должен пройти. Он имеет как минимум одну версию GCC: http://ideone.com/FPjRVN. Какую версию и платформу вы используете? –

ответ

18

Сравнение значений с плавающей запятой с использованием оператора == с большой погрешностью; два значения, которые должны равняться, могут быть не связаны с ошибками арифметического округления. Распространенный способ сравнить их заключается в использовании эпсилон:

bool double_equals(double a, double b, double epsilon = 0.001) 
{ 
    return std::abs(a - b) < epsilon; 
} 
+1

См. Более подробное обсуждение: http://www.cygnus-software.com/papers/comparingfloats/Comparing%20floating%20point%20numbers.htm#_Toc135149453 –

+1

№ «Почти равные» - это передовая техника; он не должен использоваться новичками. Одна серьезная проблема, заключающаяся в том, что «почти равный b» и «b почти равна c», не означает, что «почти равно c». –

+0

@PeteBecker Арифметика с плавающей точкой не является тривиальной, нет «способа новичков» в этом. – nijansen

3

Есть две проблемы с сравнений с плавающей точкой:

(1) операции с плавающей точкой, как правило, включают по крайней мере, крошечные ошибки округления, которые трудно предсказать , Поэтому две операции с плавающей запятой, которые должны математически давать одинаковый результат (например, 4.7 * (1.0/3.14) против 4.7/3.14), могут давать разные результаты.

(2) Компилятор разрешает выполнять операции с плавающей запятой, иногда с большей точностью, чем необходимо. Также разрешено выполнять точные операции с плавающей запятой только с той точностью, которая была необходима в другое время. Поэтому точно такая же операция может дать несколько иные результаты, что вы видите здесь.

Чтобы решить проблему OP, это выглядит так, как будто это вызвано (2). Я попытаюсь найти, есть ли какие-либо параметры компилятора, которые могут помешать компилятору использовать более высокую точность, чем это необходимо.

Смежные вопросы