Epsilon and float comparison

Started by
10 comments, last by C0D1F1ED 18 years, 11 months ago
I read this article on why not to use epsilon to compare floats. I had always used epsilon for float comparison, but am now wondering if the methods in that article are truly better. Is epsilon good enough for general game programming problems? Or is the method in that article better? Slaru
Advertisement
The method in the article is probably better, however it looks like it might be possible for that function to divide by zero, I'm not sure.

The problem is that floating point numbers aren't linear. The number of representable numbers between 0.4 and 0.5 is a lot more than the number of representable numbers between 100.4 and 100.5. As such, epsilon, the smallest representable number, isn't constant constant. If you use too small a value, large numbers won't appear equal when they should be. Too large a value, and small numbers will appear equal when they shouldn't be. Their method attempts to fix this.

Another problem with both methods is that you can get a situation when A equals B, B equals C, but C doesn't equal A.
Chess is played by three people. Two people play the game; the third provides moral support for the pawns. The object of the game is to kill your opponent by flinging captured pieces at his head. Since the only piece that can be killed is a pawn, the two armies agree to meet in a pawn-infested area (or even a pawn shop) and kill as many pawns as possible in the crossfire. If the game goes on for an hour, one player may legally attempt to gouge out the other player's eyes with his King.
Is there any benefit to using this method over the epsilon method? Or should I stick to the common epsilon method? Or is this one of those "it's up to you" questions?

Slaru
I'm concerned about the amount of time it will take to move the float from the SSE unit or FPU to the ALU registers. I don't think you can do a register to register move (meaning it will hit main memory, and be very slow).
- The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara
Quote:Original post by Magmai Kai Holmlor
I'm concerned about the amount of time it will take to move the float from the SSE unit or FPU to the ALU registers. I don't think you can do a register to register move (meaning it will hit main memory, and be very slow).


If one were to use this method, then they would profile afterwards to see if this was a real bottleneck or an imaginary one. So while it is nice to note possible performance concerns, I wouldn't make it a primary factor in determining it's usage till after profiling.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.

I've come to a decision. I have a function that tests to floats for equality taking into account the innaccuracies of floats called Equals. Later, when I have more of my game done (wish me luck, I'm rewriting my game from scratch), I will profile then with both implementations (epsilon and the article's) to see if there are any performance problems. If not, I will decide then.

Thanks for the input.

Slaru
Quote:Original post by Washu
If one were to use this method, then they would profile afterwards to see if this was a real bottleneck or an imaginary one. So while it is nice to note possible performance concerns, I wouldn't make it a primary factor in determining it's usage till after profiling.

Completely disagree. Performance checking for such an elementary operation as a compare is absolutely vital beforehand. Such operations will 100% inline, and can therefore create a kind of omnipresent performance killing "fog" within a profiler. Means, it becomes almost impossible to spot them as being the bottleneck, since every single compare operations adds a little, but none enough to stick out alone.

And even if you finally indentify them as being the bottleneck, good luck replacing an approximate floating point comparison operation with an optimized function afterwards - a function that will most certainly behave completely differently from a mathematical point of view, especially in special case situations. Welcome to a total and complete nightmare (we had a similar situation once, and ended up rewriting around 50k lines of code).

Conclusion: such elementary operations should be timed before committing to their use in production level code. They're almost always a one way street - once you start using them, there's no way back.

Edit, just to clarify: I fully agree with Washu that some types of "premature optimization" (God, I hate this term ;) can indeed be harmful. But this is definitely not such a case.
Quote:Original post by Yann L
* various things that I can agree with

Conclusion: such elementary operations should be timed before committing to their use in production level code. They're almost always a one way street - once you start using them, there's no way back.

Yes, the problem is, the majority of people don't KNOW how to properly profile an operation (how many times have you seen a for loop that runs a million times and just calls a function without doing anything with the result?). More importantly, he must make the determination if he wants accuracy or if a less accurate method will do. Performance isn't everything. Especially with what compilers can do these days.

But, more importantly, while my statement did imply that he should use it in his code and then profile. It could be taken to mean that he should profile it in a test case first before deciding that it is a bottleneck, which was the spirit of my message. One that I see you agree with.
Quote:Edit, just to clarify: I fully agree with Washu that some types of "premature optimization" (God, I hate this term ;) can indeed be harmful. But this is definitely not such a case.


This is such a case, it's just that the profiling takes place in a test case, not in the application.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.

Quote:Original post by Washu
Yes, the problem is, the majority of people don't KNOW how to properly profile an operation (how many times have you seen a for loop that runs a million times and just calls a function without doing anything with the result?).

Well, Magmai mentioned SSE to FPU register transfer times, so I guess he knows how to time such an operation ;) Generally, I agree with you though.

Quote:Original post by Washu
More importantly, he must make the determination if he wants accuracy or if a less accurate method will do. Performance isn't everything. Especially with what compilers can do these days.

It's not accuracy vs. less accuracy in this specific case. It's about two different methods, that will most of the time deliver very similar results, but will behave completely different in special case scenarios (handling of denormalized numbers, for example). If one uses the accurate but slow version, yet realizes afterwards that it turns out to be a bottleneck, then replacing it with a faster version can break an entire application. So one should make sure that the speed is appropriate beforehand, before reyling on it in development. That's what I meant by saying it's a one way street.

Quote:
This is such a case, it's just that the profiling takes place in a test case, not in the application.

Well, the term "bottleneck" usually implies in-application testing, unless you know beforehand what you're going to use the operation for. But this is almost impossible to say with elementary multipurpose operations such as a compare. Who knows how it will be used at a later point in time ?

Anyway, my point was simply that such operations should be treated as time critical from the beginning on. And that possible optimizations should be done before starting to use the operation for actual developement. If this is what you meant in your post, then accept my appologies :)
Quote:Original post by Yann L
Quote:Original post by Washu
Yes, the problem is, the majority of people don't KNOW how to properly profile an operation (how many times have you seen a for loop that runs a million times and just calls a function without doing anything with the result?).

Well, Magmai mentioned SSE to FPU register transfer times, so I guess he knows how to time such an operation ;) Generally, I agree with you though.

Indeed, I assume he does, but does the OP?
Quote:
It's not accuracy vs. less accuracy in this specific case. It's about two different methods, that will most of the time deliver very similar results, but will behave completely different in special case scenarios (handling of denormalized numbers, for example). If one uses the accurate but slow version, yet realizes afterwards that it turns out to be a bottleneck, then replacing it with a faster version can break an entire application. So one should make sure that the speed is appropriate beforehand, before reyling on it in development. That's what I meant by saying it's a one way street.

My bad, I didn't give a close enough look to the other operation presented in the article.
Quote:
Well, the term "bottleneck" usually implies in-application testing, unless you know beforehand what you're going to use the operation for. But this is almost impossible to say with elementary multipurpose operations such as a compare. Who knows how it will be used at a later point in time ?

Welcome to the wonderful world of programming eh?
Quote:
Anyway, my point was simply that such operations should be treated as time critical from the beginning on. And that possible optimizations should be done before starting to use the operation for actual developement. If this is what you meant in your post, then accept my appologies :)


Oh, I agree that the operation must be treated as performance critical, but does he KNOW that it's a bottleneck? I mean, Magmai mentioned register transfers. But most likely the numbers will be in the cache, and thus main memory won't be hit at all. More importantly, one should look at the assembly output as well, which is another form of profiling. Also, there are instructions like FEMMS which can enable a fast switch between MMX and FP.

As far as what I meant about the test case: Yes, that is what I meant. The test case is a platform for testing and optimizing the operation. There are only so many different ways one can use a comparison operation and have a meaningful result. The places it may be used vary, but the usage does not. So, a test case can be an easy way to profile and optimize an operation that will be commonly abused.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.

This topic is closed to new replies.

Advertisement