views:

1430

answers:

6

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.

+1  A: 

Generally, in any language, you can't really count on equality of float-like types. In your case since it looks like you have more control, it does appear that 0.1 is not float by default. You could probably find that out with sizeof(0.1) (vs. sizeof(self.scroller.currentValue).

Lou Franco
that's a good idea! I didn't think of sizeof()
Dimitris
sizeof reveals that 0.1 is a double. It is still very strange that you don't get equality when both have the value 0.10000 isn't it?
Dimitris
@Dimitris, it's not that strange. See @MarkPowell's answer.
Carl Norum
Chuck
+3  A: 

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.

MarkPowell
+6  A: 

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.

Georg
+1 for using single precision literals.
Stephen Canon
+3  A: 

ANSI C 3.1.3.1 defines "an unsuffixed floating constant" as a double. This can often be quite annoying in cases where an implicit cast should occur IMO (such as passing 0.1 to a function that takes a float). But it is the case.

As gs notes, "0.1f" is a better way to cast the numeric constant to a float.

You should never try to check floating point numbers for equality as you've discovered. The correct way to determine if two floating points are "equal" is to compare their absolute difference to epsilon (the smallest possible float), which is generally "close enough" even for a double unless you're actually doing high-precision math. Personally I use the following two macros for this:

#define fequal(a,b) (fabs((a) - (b)) < FLT_EPSILON)
#define fequalzero(a) (fabs(a) < FLT_EPSILON)

To compare less-than-or-equal, you can create a macro like the above that tests for less than "b + FLT_EPSILON".

To protect you against accidentally comparing two floating point numbers, you should turn on -Wfloat-equal by adding it to WARNING_CFLAGS in your xcconfig file.

Rob Napier
Thanks for all that. A lot of good answers here. I was aware of all the theory but I thought the compiler took care of the details and let us do such simple comparisons easily. I think 0.1f as proposed by gs a good quick solution. Comparing against the machine's epsilon is a bit too much for this specific app. Maybe for something more scientific.
Dimitris
Comparison against epsilon is exactly for non-scientific applications. It's because in most "business-like" applications, you don't typically care what the number actually is. Using the trailing "f" is not sufficient to ensure that two numbers you think are the same are actually going to be equal. It happens to work in this case because you're getting lucky about how the math worked out.
Rob Napier
In his case it's probably not necessary. He does not only compare it to 0.1. He actually looks if its 0.1 _or less_.
Georg
@Rob: It's reasonable to suppose that, if you convert 0.1 to a float twice, the representations will be the same, and they will register as equal. There are a few other cases in which it works (integral floating-point values under addition, subtraction, and multiplication), but in general it doesn't.
David Thornley
Regarding "or less," you can still get the error (as he did initially) if the value is within epsilon. Regarding special cases where it will work out, it's true that if you perform the same operations you'll get to the same place. But as with many issues, it is best to program defensively unless there is a strong reason to do otherwise. At the point of the comparison, you do not know how the numbers were created. If the generation code changes, you will get "spooky action at a distance" bugs. Doing it correctly all the time has low cost and protects against this. Thus we have the warning.
Rob Napier
+2  A: 

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.

David Thornley
Following this thought in decimal (which has different hard numbers than binary, but the concept is the same) you will find that (1/3)*3 != 1, no matter how many (finite) digits you choose. This is why doing all your math in float or double doesn't really fix the problem.
Rob Napier
Right. Of course, you can get arbitrarily close to 1 by using enough digits, so testing for getting really close works a lot better than testing for equality. The real issue here is that floating-point equality tests do not in general work.
David Thornley
Agreed. Thus the available warning (which I recommend turning on) to prevent you from accidentally using floating point equality.
Rob Napier
+1  A: 

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.

epatel