20

Look at this code:

#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);
}

The output is 84.78 == 84.78 : 0 which means the same method doesn't return the same value every time, even though all parameters are static?

But if I write 3.0 instead of 3.14 in the V() method definition, like this:

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

Then, the output is: 84.78 == 84.78 : 1

What is going on here? I need this method, for my program, which will compare volumes of two objects, but it is impossible? I banged my head for so long to figure out what is the cause of the problem and luckily I found it, but now I don't understand why?? Does it have something to do with the compiler (GCC) or am I missing something important here?

tuks
  • 800
  • 3
  • 11
  • 27
  • 3
    you simply don't test floating point number for equality like that. – yngccc Sep 24 '13 at 01:32
  • @yngum why? how should I? – tuks Sep 24 '13 at 01:36
  • 1
    Usually it's a bad idea to test floating point values for equality, since small rounding errors can give unexpected results. But, as you say, this does the same calculation twice with the same input, so the test should pass. It does with at least one version of GCC: http://ideone.com/FPjRVN. What version and platform are you using? – Mike Seymour Sep 24 '13 at 01:37
  • @MikeSeymour `gcc version 4.7.2 (Debian 4.7.2-5)`, Debian Wheezy. But the code will be tested in Visual Studio later, should I just ignore this for now? It's a part of my homework. I don't remember professor saying anything about not comparing double values this way – tuks Sep 24 '13 at 01:43
  • 1
    @tuks: Your professor might not have said anything, but another said rather a lot: http://www.cl.cam.ac.uk/teaching/1011/FPComp/floatingmath.pdf – Mike Seymour Sep 24 '13 at 01:46
  • @MikeSeymour thanks. I guess the better way would be to just compare radiuses instead of volumes... – tuks Sep 24 '13 at 01:53
  • There is no requirement that the same calculation produces the same result twice. Strange but true. Any calculation can be performed in higher precision than mandated at any time, and this can change the result. Simply do not ever compare two floats for equality. Subtract them and compare their difference to a small quantity. – Eric Lippert Sep 24 '13 at 02:04
  • @EricLippert I found a solution: `int(V()*1000) == int(s.V()*1000)` – tuks Sep 24 '13 at 02:36
  • @tuks, BTW there is a bug in this code. The volume of a sphere of radius three is 113.04, not 84.78. You should check your calculations by hand. – Adam Burry Sep 24 '13 at 03:50
  • @tuks That's not a solution. – David Heffernan Sep 24 '13 at 08:14
  • 1
    @AdamBurry yeah I see... it's because of (4/3), it should be (4.0/3) – tuks Sep 24 '13 at 11:14
  • @DavidHeffernan yeah I guess it's bad... `abs(V()-s.V())<0.0001` is probably the best – tuks Sep 24 '13 at 11:19
  • Whooops, meant to select this question: http://stackoverflow.com/questions/21346694/floating-point-arithmetic-and-comparing-of-floating-point-values. Well, close enough I guess. – Mooing Duck Jul 10 '14 at 00:55
  • Similar to https://stackoverflow.com/questions/17333/what-is-the-most-effective-way-for-float-and-double-comparison – sancho.s ReinstateMonicaCellio Apr 28 '19 at 02:28

2 Answers2

37

Comparing floating point values using the == operator is very error prone; two values that should be equal may not be due to arithmetic rounding errors. The common way to compare these is to use an epsilon:

bool double_equals(double a, double b, double epsilon = 0.001)
{
    return std::abs(a - b) < epsilon;
}
nikolas
  • 8,707
  • 9
  • 50
  • 70
  • 1
    See for more detailed discussion: http://www.cygnus-software.com/papers/comparingfloats/Comparing%20floating%20point%20numbers.htm#_Toc135149453 – Adam Burry Sep 24 '13 at 13:45
  • 2
    No. "Almost equals" is an advanced technique; it should not be used by beginners. One serious problem that it has it that `a almost equals b` and `b almost equals c` does not imply that `a almost equals c`. – Pete Becker Sep 24 '13 at 13:53
  • 3
    @PeteBecker Floating point arithmetics are not trivial, there is no "beginner's way" of doing this. – nikolas Sep 24 '13 at 14:02
  • @nijansen - true; but "almost equals" is almost always **not** the right solution. – Pete Becker Sep 24 '13 at 14:03
  • 2
    I wouldn't expect double_equals (1e-6, -1e-5) to return "true". – gnasher729 Jul 10 '14 at 00:42
4

There are two problems with floating point comparisons:

(1) Floating point operations usually involve at least tiny rounding errors which are hard to predict. Therefore two floating point operations that should mathematically give the same result (like 4.7 * (1.0 / 3.14) vs. 4.7 / 3.14) may give different results.

(2) The compiler is allowed to do floating point operations sometimes with higher precision than necessary. It is also allowed to do the exact same floating point operations with just the precision that was necessary at other times. Therefore the exact same operation may produce slightly different results, which is what you see here.

To solve the OP's problem, this looks like it is caused by (2). I'd try to find if there are any compiler options that can prevent the compiler from using higher precision than needed.

gnasher729
  • 51,477
  • 5
  • 75
  • 98