Strange problem comparing floats in objective-C

27,523

Solution 1

I believe, having not found the standard that says so, that when comparing a float to a double the float is cast to a double before comparing. Floating point numbers without a modifier are considered to be double in C.

However, in C there is no exact representation of 0.1 in floats and doubles. Now, using a float gives you a small error. Using a double gives you an even smaller error. The problem now is, that by casting the float to a double you carry over the bigger of error of the float. Of course they aren't gone compare equal now.

Instead of using (float)0.1 you could use 0.1f which is a bit nicer to read.

Solution 2

The problem is, as you have suggested in your question, that you are comparing a float with a double.

There is a more general problem with comparing floats, this happens because when you do a calculation on a floating point number the result from the calculation may not be exactly what you expect. It is fairly common that the last bit of the resulting float will be wrong (although the inaccuracy can be larger than just the last bit). If you use == to compare two floats then all the bits have to be the same for the floats to be equal. If your calculation gives a slightly inaccurate result then they won't compare equal when you expect them to. Instead of comparing the values like this, you can compare them to see if they are nearly equal. To do this you can take the positive difference between the floats and see if it is smaller than a given value (called an epsilon).

To choose a good epsilon you need to understand a bit about floating point numbers. Floating point numbers work similarly to representing a number to a given number of significant figures. If we work to 5 significant figures and your calculation results in the last digit of the result being wrong then 1.2345 will have an error of +-0.0001 whereas 1234500 will have an error of +-100. If you always base your margin of error on the value 1.2345 then your compare routine will be identical to == for all values great than 10 (when using decimal). This is worse in binary, it's all values greater than 2. This means that the epsilon we choose has to be relative to the size of the floats that we are comparing.

FLT_EPSILON is the gap between 1 and the next closest float. This means that it may be a good epsilon to choose if your number is between 1 and 2, but if your value is greater than 2 using this epsilon is pointless because the gap between 2 and the next nearest float is larger than epsilon. So we have to choose an epsilon relative to the size of our floats (as the error in the calculation is relative to the size of our floats).

A good(ish) floating point compare routine looks something like this:

bool compareNearlyEqual (float a, float b, unsigned epsilonMultiplier)       
{
  float epsilon;
  /* May as well do the easy check first. */
  if (a == b)
    return true;

  if (a > b) {
    epsilon = scalbnf(1.0f, ilogb(a)) * FLT_EPSILON * epsilonMultiplier;
  } else {
    epsilon = scalbnf(1.0, ilogb(b)) * FLT_EPSILON * epsilonMultiplier;
  }

  return fabs (a - b) <= epsilon;
}

This comparison routine compares floats relative to the size of the largest float passed in. scalbnf(1.0f, ilogb(a)) * FLT_EPSILON finds the gap between a and the next nearest float. This is then multiplied by the epsilonMultiplier, so the size of the difference can be adjusted, depending on how inaccurate the result of the calculation is likely to be.

You can make a simple compareLessThan routine like this:

bool compareLessThan (float a, float b, unsigned epsilonMultiplier)
{
  if (compareNearlyEqual (a, b, epsilonMultiplier)
    return false;

  return a < b;
}

You could also write a very similar compareGreaterThan function.

It's worth noting that comparing floats like this may not always be what you want. For instance this will never find that a float is close to 0 unless it is 0. To fix this you'd need to decide what value you thought was close to zero, and write an additional test for this.

Sometimes the inaccuracies you get won't depend on the size of the result of a calculation, but will depend on the values that you put into a calculation. For instance sin(1.0f + (float)(200 * M_PI)) will give a much less accurate result than sin(1.0f) (the results should be identical). In this case your compare routine would have to look at the number you put into the calculation to know the margin of error of the answer.

Solution 3

Doubles and floats have different values for the mantissa store in binary (float is 23 bits, double 54). These will almost never be equal.

The IEEE Float Point article on wikipedia may help you understand this distinction.

Solution 4

In C, a floating-point literal like 0.1 is a double, not a float. Since the types of the data items being compared are different, the comparison is done in the more precise type (double). In all implementations I know about, float has a shorter representation than double (usually expressed as something like 6 vs. 14 decimal places). Moreover, the arithmetic is in binary, and 1/10 does not have an exact representation in binary.

Therefore, you're taking a float 0.1, which loses accuracy, extending it to double, and expecting it to compare equal to a double 0.1, which loses less accuracy.

Suppose we were doing this in decimal, with float being three digits and double being six, and we were comparing to 1/3.

We have the stored float value being 0.333. We're comparing it to a double with value 0.333333. We convert the float 0.333 to double 0.333000, and find it different.

Solution 5

0.1 is actually a very dificult value to store binary. In base 2, 1/10 is the infinitely repeating fraction

0.0001100110011001100110011001100110011001100110011...

As several has pointed out, the comparison has to made with a constant of the exact same precision.

Share:
27,523
Dimitris
Author by

Dimitris

Developer, manager, maker of things. SOreadytohelp

Updated on July 10, 2022

Comments

  • Dimitris
    Dimitris almost 2 years

    At some point in an algorithm I need to compare the float value of a property of a class to a float. So I do this:

    if (self.scroller.currentValue <= 0.1) {
    }
    

    where currentValue is a float property.

    However, when I have equality and self.scroller.currentValue = 0.1 the if statement is not fulfilled and the code not executed! I found out that I can fix this by casting 0.1 to float. Like this:

    if (self.scroller.currentValue <= (float)0.1) {
    }
    

    This works fine.

    Can anyone explain to my why this is happening? Is 0.1 defined as a double by default or something?

    Thanks.