Navigation



3.5 — Relational operators (comparisons)

There are 6 relational operators:

Operator Symbol Form Operation
Greater than > x > y true if x is greater than y, false otherwise
Less than < x < y true if x is less than y, false otherwise
Greater than or equals >= x >= y true if x is greater than or equal to y, false otherwise
Less than or equals <= x <= y true if x is less than or equal to y, false otherwise
Equality == x == y true if x equals y, false otherwise
Inequality != x != y true if x does not equal y, false otherwise

You have already seen how all of these work, and they are pretty intuitive. Each of these operators evaluates to the boolean value true (1), or false (0).

Keep in mind that comparing floating point values using any of these operators is dangerous. This is because small rounding errors in the floating point operands may cause an unexpected result. See the section on floating point numbers for more details.

However, sometimes the need to do floating point comparisons is unavoidable. In this case, the less than and greater than operators (>, >=, <, and <=) are typically used with floating point values as normal. The operators will produce the correct result most of the time, only potentially failing when the two operands are almost identical. Due to the way these operators tends to be used, a wrong result typically only has slight consequences.

The equality operator is much more troublesome since small rounding errors make it almost useless. Consequently, using the == operator on floating point numbers is not advised. The most common method of doing floating point equality involves using a function that calculates how close the two values are to each other. If the two numbers are "close enough", then we call them equal.

Donald Knuth, a famous computer scientist, suggested the following method in his book “The Art of Computer Programming, Volume II: Seminumerical Algorithms (Addison-Wesley, 1969)”:

bool IsEqual(double dX, double dY)
{
    const double dEpsilon = 0.000001; // or some other small number
    return fabs(dX - dY) <= dEpsilon * fabs(dX);
}

dEpsilon is a very small value (eg. 0.000001) that is used to help define what “close enough” is. fabs() is a function in the standard library (#include <cmath>) that returns the absolute value of it’s double parameter.

Let’s examine how the IsEqual() function works. On the left side of the <= operator, the absolute value of dX - dY tells how close dX and dY are to each other as a positive number.

It is easiest to think of dEpsilon as a percentage. A dEpsilon of 0.01 means that dX and dY have to be within 1% of each other in order to be considered equal. On the right side of the <= operator, we multiply dEpsilon by fabs(dX) to find the largest distance the two numbers can be apart and still be considered equal. For example, if fabs(dX) evaluates to 1000, and dEpsilon is 0.01, the largest distance apart the two numbers can be is 10.

Finally, we compare the distance between dX and dY with the largest distance apart that they can still be considered "close enough". If they are close enough, the function returns true. Otherwise, it returns false.

The value for dEpsilon can be adjusted to whatever is most appropriate for the program. Often, programmers will make dEpsilon a third parameter of IsEqual() so it can be defined on a call-by-call basis.

To do inequality (!=) instead of equality, simply call this function and use the logical NOT operator (!) to flip the result:

if (!IsEqual(dX, dY))
    cout << dX << " is not equal to " << dY << endl;
3.6 -- Logical operators
Index
3.4 -- Sizeof, comma, and arithmetic if operators

26 comments to 3.5 — Relational operators (comparisons)

  • If a float is generally accurate to 7 decimal places, and a double is at 16, if I were working with figures with 2 or 4 decimal places, would the accuracy ever really be an issue? For example, would I see something like
    (.0095 * 36.75) > 34.90
    equate to false since it uses less than the 7 decimals that floats tend to be accurate to?

  • A float is not accurate to 7 decimal places. A float is accurate to approximately 7 significant digits. A significant digits is any digit that is not a placeholder 0, including ones on the left side of the decimal.

    For example, .0095 has two placeholder zeros, and so is only 2 significant figures. 34.90 has 4 significant figures.

    There are two types of errors we need to watch out for with floating point values: rounding errors, and precision errors.

    Rounding errors can happen with numbers of any length, because some numbers have infinite representations in binary (0.1 for example), and those representations will be truncated. Rounding errors typically make your answer wrong by 0.000001, or some small number like that.

    The second and more potentially serious error are precision errors, where your number can’t be stored because the floating point representation doesn’t have enough memory. Precision errors are more serious because they can affect your answer by a much larger degree of magnitude than rounding errors.

    0.0095 * 36.75 = 0.349125, which is 6 significant figures, so in this case, you’ll probably be fine in terms of precision errors. Consequently, your answer will only be affected by small rounding errors.

    But consider a case like: 0.0095 * 36.7513. Even though 36.7513 is 4 decimal places, it’s 6 significant digits. When multiplied by 0.0095, the answer is 0.34913735. A float will truncate this to 0.349137.

    Once you get into larger dollar amounts, floats are even less suitable. Consider $100264.75. This is a number with 8 significant digits, even though it only uses 2 decimal places. Already a float is not going to be able to hold this number. As you do mathematical operations on it, it’s going to drift farther and farther from your intended answer.

    In short, if accuracy is important, use double. Only use floats when accuracy is not that important (eg. in games, where it doesn’t really matter if your character has 137.24 or 137.25 strength).

    • Jordan15

      doesn’t 34.90 have 3 significant digits?

      • profanegod

        I know math teachers usually say that the last zeros don’t matter after a decimal, but when you’re talking about precision they do. In sciences when you’re taking measurements a .39 may be the same value as .3900000 mathematically speaking, but the latter is more precise to 7 significant figures. With the .39 value you cannot be assured the number in the thousandths place is 0.

  • [...] 2007 Prev/Next Posts « 3.5 — Relational operators (comparisons) | Home | 3.7 — Converting between binary and decimal » Friday, June 15th, 2007 at [...]

  • Stuart

    Alex, you’ve made an error in your IsEqual function.
    With a dEpsilon of 0.1, if dX is 150 and dY is 135, it will evaluate to true; but if dX is 135 and dY is 150 (the other way round), it will evaluate to false.
    This is because your dEpsilon is only working on dX; not both dX and dY.

    I’ve rewrote your function like this:

    bool IsEqual(double dX, double dY)
    {
    const double dEpsilon = 1; // dX and dY have to be within 1% of eachother
    return fabs(dX – dY) <= dEpsilon * ((dX + dY) / 100);
    }

    I’ve tested this and it works fine. (99 will equal 100, but 98 will not equal 100.)

    ; )

    • Stuart

      BTW, I wish my tags would work. Don’t know what the problem is. : (

    • I didn’t write that function, Donald Knuth did. :) You’re correct in that it isn’t symmetric though. I’m not sure why your tags aren’t working either. Seems to work for some people and not others and I still haven’t isolated why.

      • Stuart

        Sorry, LOL. I’d been struggling with it for a while trying to fully understand how it works. . . .

        PS. I wouldn’t've been able to code that function without your teachings. ;D

    • SomeReader

      To be totally accurate, it is needed to say that the given function with dEpsilon == 1.0 gives precision of 2.0202…% (left side equals to right side of equation when x={+-}101y/99 ~= {+-}1.0202…).

      To be symmetric, the function should compare the distance between dX and dY with some percent to an average between dX and dY:

      return fabs(dX-dY) <= fabs(dX+dY)/2 / 100 * dEpsilon; // if dEpsilon is in percents

      In this case, dEpsilon will be the precision factor. Just to optimize the function, it better should be re-written into:

      bool isEqual(double dX, double dY, double dPrecision = 0.01) {
          return fabs(dX-dY) <= fabs(dX+dY)/2 * dPrecision;
      };

      On the other hand, it may be not fully correct to compare distance between dX and dY with an average on them. In some very precise calculation the comparing value should not affect the testing function. In this case it may be more correct to use the function given by Donald Knuth, I guess:

      bool isEqual(double a, double b, double precision = 0.000001) {
          return fabs(a - b) <= precision * fabs(a);
      };

      and remember that the comparing value should be the second argument to this function when the first argument must be the value which one that is compared to.

  • TBM

    I don’t really understand what is happening with fabs(). For example if dX = 2 and dY = 3 how would I figure what the result of fabs(dX), or fabs(dX – dY) would/should be. The sentence that states that fabs() is a function that returns the absolute value of it’s double parameter leads me to believe that dX would evaluate to 4 and dY to 6 in this case, but when compiled and ran with the above values and sending fabs(dX) or fabs(dY) to the screen using cout I get whatever the value of dX or dY was to begin with. When doing the same with fabs(dX – dY) however I get a value of 1, which would be the same as (dX – dY) anyway. (scratches head)

    • Quinn

      Heh, that’s not what he meant. :) The double parameter he was referring to was the double float point variable that is provided to fabs as a parameter. So fabs doesn’t return double the value put into it, it just returns the absolute value of the number sent to it. You can think of fabs doing something similar to this, though this is REALLY simplified:

      double fabs(double x)
      {
          if (x < 0) {
              return -x; //The value must be negative, therefore return that value times -1.
          } else {
              return x; //No changes needed, the value is positive.
          }
      }

      And remember, when you’re using functions, the mathematical expressions inside their parameters are evaluated before the function call is, for example using “fabs(-4 – 6)” is the same as “fabs(-10)”.

  • auldy66

    can anyone please tell me why this returns a syntax error in line 12 , the return stataement?
    thanx in advance
    Auldy66
    Ps: Well done Alex , made me start hammering the keys after a lot of years..

    #include "stdafx.h"
    #include "cmath"
    #include <iostream>
    
    //tests to see if 2 numbers are close enough to call equal
    bool IsEqual(double dX, double dY);
    	const double dEpsilon = 0.000001; // or some other small number
    	return fabs(dX - dY) <= dEpsilon * fabs(dX)
    
    //testing function above
    int main()
    {
    	using namespace std;
    	int dX = 0, dY = 0;
    
    	cout << "Enter a value for X: " << endl;
    	cin >> dX;
    	cout << "Enter a value for Y: " << endl;
    	cin >> dY;
    
    	if (IsEqual(dX, dY))
    	{
    		cout << dX << " is equal to " << dY << endl;
    	}
    	system("pause");
    	return 0;
    }
    
    • See ++

      Hi auldy66, try this one. I hope I helped you.

      #include<iostream>
      #include<cmath>
      using namespace std;
      
      bool IsEqual(double x,double y);
      
      int main()
      {
      	double w,z;
      
      	cout<<"Enter a value for w: ";
      	cin>>w;
      	cout<<"Enter a value for z: ";
      	cin>>z;
      
      	bool answer=IsEqual(w,z);
      	if(answer)
      		cout<<w<<" is equal to "<<z<<endl;
      	else
      		cout<<w<<" is not equal to "<<z<<endl;
      
      	return 0;
      }
      
      bool IsEqual(double x,double y)
      {
      	const double Epsilon=0.000001;
      	return fabs(x-y)<=Epsilon*fabs(x);
      }
      
    • Scott

      Hi Auldy66,
      it looks like you did not make your IsEqual function correctly. It is missing brackets, it should not have a semicolon at the end of the fucntion name line number 1 , and the return needs a semicolon. Not sure if they were left out of your code or just a result of entering into the web page. I changed your function as shown below and it worked just fine.
      Regards,
      Scott

      bool IsEqual(double dX, double dY)
      {
      	const double dEpsilon = 0.000001; // or some other small number
      	return fabs(dX - dY) <= dEpsilon * fabs(dX);
      )
  • Fluke

    I dont get it.

    If we have this for example

    	const double Epsilon=0.0001;
    	return fabs(x-y)<=Epsilon*fabs(x);

    And we test with x=20.111 and y=20.1101 (close enought) we get this:

    20.001-20.0001 <= 0.0001*20.001
    0.0009 <= 0.0020001

    that equals true.

    But lets use smaller numbers, that are close to each other as on the first example: x=0.001, y=0.0001, we get this:

    0.001-0.0001 <= 0.0001*0.001
    0.0009 <= 0.0000001

    that equals false.

    For me personaly, in both examples the numbers are satisfying close.

    If i would need to compare two close floating point values, i would go for this:

    int nFloatPPrecis = 100; //i need 2 digits after the floating point
    return ((int)(x*nFloatPPrecis) - (int)(y*nFloatPPrecis)) == 0)

    If i need more precission i would increase the nFloatPPrecis depending on how much digits i would need.

    What do you think?

  • Tom

    I find it very curious that “absolute value” is a function in the standard library, but not a built-in operator in C++. You would think that the (otherwise useless) unary “+” operator would be assigned as the operator for the absolute value function. In other words, it seems logical that the expression +(x – y) should evaluate to the absolute value of (x – y).

    In any case Alex, I think it would be better to introduce the absolute value function in section 3.2 with the Operators.

    Keep up the good work.

  • Darek

    Guys, I’ll tell you what I think, if I may. The function that Knuth made has one problem to my mind: it takes only the first value passed in into consideration when calculating the relative distance between the variables. As a mathematician, I think it should take both of them but – attention! – instead of using (x+y) it should make use of max(|x|,|y|). Therefore, you look for this piece of code, I believe:

    bool IsEqual( double x,double y, double epsilon = 1e-6 )
    {
        return fabs( x - y ) <= epsilon * max( fabs(x), fabs(y) );
    }
    

    By the way, I’m sure there must be some kind of max function in C++ but don’t know in which library. I have not analysed the function thoroughly as it should be but I think it’s the best version so far. Will gladly see a better one.

    • Kush

      This seems to be best function.
      I think there is max function somewhere nearby, probabaly in stdlib.h
      but it can be easily written off:

      float max (float dx, float dy)
      {
      return ( (dx>dy) ? dx:dy );
      }
      
  • [...] 3.5 Relational operators (comparisons) [...]

  • Tom

    Due to the way these operators tends to be used, a wrong result typically only has slight consequences.
    tend

  • life

    Please can anyone specify why we multiply dEpsilon with dX? What is the purpose?
    Thanks. Note: Even if you see this message has been sent out quiet long time ago, your answer will be ok for me.


    bool IsEqual(double dX, double dY)
    {
    const double dEpsilon = 0.000001; // or some other small number
    return fabs(dX - dY) <= dEpsilon * fabs(dX);
    }

  • [...] section on relational operators has more detail on comparing floating point [...]

  • personguy

    this is probably a silly question, but i keep reading this thinking i’m missing something really obvious. when you use maths in your program, after some point 2+2 will no longer equal 4? .. i like to think of a computer as a pretty dependable calculator, so this discussion of two numbers being “close enough” within a margin of 1% is blowing my mind.

You must be logged in to post a comment.